<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
  <channel xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <title>ABP.IO Stories</title>
    <link>https://abp.io/community/articles</link>
    <description>A hub for ABP Framework, .NET, and software development. Access articles, tutorials, news, and contribute to the ABP community.</description>
    <lastBuildDate>Fri, 23 Oct 2026 03:03:08 Z</lastBuildDate>
    <generator>Community - ABP.IO</generator>
    <image>
      <url>https://abp.io/assets/favicon.ico/favicon-32x32.png</url>
      <title>ABP.IO Stories</title>
      <link>https://abp.io/community/articles</link>
    </image>
    <a10:link rel="self" type="application/rss+xml" title="self" href="https://abp.io/community/rss?member=m.aliozkaya" />
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/repository-pattern-in-asp.net-core-2dudlg3j</guid>
      <link>https://abp.io/community/posts/repository-pattern-in-asp.net-core-2dudlg3j</link>
      <a10:author>
        <a10:name>m.aliozkaya</a10:name>
        <a10:uri>https://abp.io/community/members/m.aliozkaya</a10:uri>
      </a10:author>
      <category>EfCore</category>
      <category>entity-framework-core</category>
      <category>repository</category>
      <category>abp-framework</category>
      <category>aspnet-core</category>
      <title>Repository Pattern in ASP.NET Core</title>
      <description>Learn how to keep your ASP.NET Core projects clean and testable using the Repository Pattern. This post walks through a simple product example with Entity Framework, explains how to structure repositories and services, and shows how the ABP Framework provides built-in repositories that make the same pattern even easier to use.
</description>
      <pubDate>Sun, 02 Nov 2025 07:48:14 Z</pubDate>
      <a10:updated>2026-10-23T02:12:51Z</a10:updated>
      <content:encoded><![CDATA[<h1>Repository Pattern in the ASP.NET Core</h1>
<p>If you’ve built a .NET app with a database, you’ve likely used Entity Framework, Dapper, or ADO.NET. They’re useful tools; still, when they live inside your business logic or controllers, the code can become harder to keep tidy and to test.</p>
<p>That’s where the <strong>Repository Pattern</strong> comes in.</p>
<p>At its core, the Repository Pattern acts as a <strong>middle layer between your domain and data access logic</strong>. It abstracts the way you store and retrieve data, giving your application a clean separation of concerns:</p>
<ul>
<li><strong>Separation of Concerns:</strong> Business logic doesn’t depend on the database.</li>
<li><strong>Easier Testing:</strong> You can replace the repository with a fake or mock during unit tests.</li>
<li><strong>Flexibility:</strong> You can switch data sources (e.g., from SQL to MongoDB) without touching business logic.</li>
</ul>
<p>Let’s see how this works with a simple example.</p>
<h2>A Simple Example with Product Repository</h2>
<p>Imagine we’re building a small e-commerce app. We’ll start by defining a repository interface for managing products.</p>
<p>You can find the complete sample code in this GitHub repository:</p>
<p>https://github.com/m-aliozkaya/RepositoryPattern</p>
<h3>Domain model and context</h3>
<p>We start with a single entity and a matching <code>DbContext</code>.</p>
<p><code>Product.cs</code></p>
<pre><code class="language-csharp">using System.ComponentModel.DataAnnotations;

namespace RepositoryPattern.Web.Models;

public class Product
{
    public int Id { get; set; }

    [Required, StringLength(64)]
    public string Name { get; set; } = string.Empty;

    [Range(0, double.MaxValue)]
    public decimal Price { get; set; }

    [StringLength(256)]
    public string? Description { get; set; }

    public int Stock { get; set; }
}
</code></pre>
<p><code>&quot;AppDbContext.cs</code></p>
<pre><code class="language-csharp">using Microsoft.EntityFrameworkCore;
using RepositoryPattern.Web.Models;

namespace RepositoryPattern.Web.Data;

public class AppDbContext(DbContextOptions&lt;AppDbContext&gt; options) : DbContext(options)
{
    public DbSet&lt;Product&gt; Products =&gt; Set&lt;Product&gt;();
}
</code></pre>
<h3>Generic repository contract and base class</h3>
<p>All entities share the same CRUD needs, so we define a generic interface and an EF Core implementation.</p>
<p><code>Repositories/IRepository.cs</code></p>
<pre><code class="language-csharp">using System.Linq.Expressions;

namespace RepositoryPattern.Web.Repositories;

public interface IRepository&lt;TEntity&gt; where TEntity : class
{
    Task&lt;TEntity?&gt; GetByIdAsync(int id, CancellationToken cancellationToken = default);
    Task&lt;List&lt;TEntity&gt;&gt; GetAllAsync(CancellationToken cancellationToken = default);
    Task&lt;List&lt;TEntity&gt;&gt; GetListAsync(Expression&lt;Func&lt;TEntity, bool&gt;&gt; predicate, CancellationToken cancellationToken = default);
    Task AddAsync(TEntity entity, CancellationToken cancellationToken = default);
    Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
    Task DeleteAsync(int id, CancellationToken cancellationToken = default);
}
</code></pre>
<p><code>Repositories/EfRepository.cs</code></p>
<pre><code class="language-csharp">using Microsoft.EntityFrameworkCore;
using RepositoryPattern.Web.Data;

namespace RepositoryPattern.Web.Repositories;

public class EfRepository&lt;TEntity&gt;(AppDbContext context) : IRepository&lt;TEntity&gt;
    where TEntity : class
{
    protected readonly AppDbContext Context = context;

    public virtual async Task&lt;TEntity?&gt; GetByIdAsync(int id, CancellationToken cancellationToken = default)
        =&gt; await Context.Set&lt;TEntity&gt;().FindAsync([id], cancellationToken);

    public virtual async Task&lt;List&lt;TEntity&gt;&gt; GetAllAsync(CancellationToken cancellationToken = default)
        =&gt; await Context.Set&lt;TEntity&gt;().AsNoTracking().ToListAsync(cancellationToken);

    public virtual async Task&lt;List&lt;TEntity&gt;&gt; GetListAsync(
        System.Linq.Expressions.Expression&lt;Func&lt;TEntity, bool&gt;&gt; predicate,
        CancellationToken cancellationToken = default)
        =&gt; await Context.Set&lt;TEntity&gt;()
            .AsNoTracking()
            .Where(predicate)
            .ToListAsync(cancellationToken);

    public virtual async Task AddAsync(TEntity entity, CancellationToken cancellationToken = default)
    {
        await Context.Set&lt;TEntity&gt;().AddAsync(entity, cancellationToken);
        await Context.SaveChangesAsync(cancellationToken);
    }

    public virtual async Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
    {
        Context.Set&lt;TEntity&gt;().Update(entity);
        await Context.SaveChangesAsync(cancellationToken);
    }

    public virtual async Task DeleteAsync(int id, CancellationToken cancellationToken = default)
    {
        var entity = await GetByIdAsync(id, cancellationToken);
        if (entity is null)
        {
            return;
        }

        Context.Set&lt;TEntity&gt;().Remove(entity);
        await Context.SaveChangesAsync(cancellationToken);
    }
}
</code></pre>
<p>Reads use <code>AsNoTracking()</code> to avoid tracking overhead, while write methods call <code>SaveChangesAsync</code> to keep the sample straightforward.</p>
<h3>Product-specific repository</h3>
<p>Products need one extra query: list the items that are almost out of stock. We extend the generic repository with a dedicated interface and implementation.</p>
<p><code>Repositories/IProductRepository.cs</code></p>
<pre><code class="language-csharp">using RepositoryPattern.Web.Models;

namespace RepositoryPattern.Web.Repositories;

public interface IProductRepository : IRepository&lt;Product&gt;
{
    Task&lt;List&lt;Product&gt;&gt; GetLowStockProductsAsync(int threshold, CancellationToken cancellationToken = default);
}
</code></pre>
<p><code>Repositories/ProductRepository.cs</code></p>
<pre><code class="language-csharp">using Microsoft.EntityFrameworkCore;
using RepositoryPattern.Web.Data;
using RepositoryPattern.Web.Models;

namespace RepositoryPattern.Web.Repositories;

public class ProductRepository(AppDbContext context) : EfRepository&lt;Product&gt;(context), IProductRepository
{
    public Task&lt;List&lt;Product&gt;&gt; GetLowStockProductsAsync(int threshold, CancellationToken cancellationToken = default) =&gt;
        Context.Products
            .AsNoTracking()
            .Where(product =&gt; product.Stock &lt;= threshold)
            .OrderBy(product =&gt; product.Stock)
            .ToListAsync(cancellationToken);
}
</code></pre>
<h3>🧩 A Note on Unit of Work</h3>
<p>The Repository Pattern is often used together with the <strong>Unit of Work</strong> pattern to manage transactions efficiently.</p>
<blockquote>
<p>💡 *If you want to dive deeper into the Unit of Work pattern, check out our separate blog post dedicated to that topic. https://abp.io/community/articles/lv4v2tyf</p>
</blockquote>
<h3>Service layer and controller</h3>
<p>Controllers depend on a service, and the service depends on the repository. That keeps HTTP logic and data logic separate.</p>
<p><code>Services/ProductService.cs</code></p>
<pre><code class="language-csharp">using RepositoryPattern.Web.Models;
using RepositoryPattern.Web.Repositories;

namespace RepositoryPattern.Web.Services;

public class ProductService(IProductRepository productRepository)
{
    private readonly IProductRepository _productRepository = productRepository;

    public Task&lt;List&lt;Product&gt;&gt; GetProductsAsync(CancellationToken cancellationToken = default) =&gt;
        _productRepository.GetAllAsync(cancellationToken);

    public Task&lt;List&lt;Product&gt;&gt; GetLowStockAsync(int threshold, CancellationToken cancellationToken = default) =&gt;
        _productRepository.GetLowStockProductsAsync(threshold, cancellationToken);

    public Task&lt;Product?&gt; GetByIdAsync(int id, CancellationToken cancellationToken = default) =&gt;
        _productRepository.GetByIdAsync(id, cancellationToken);

    public Task CreateAsync(Product product, CancellationToken cancellationToken = default) =&gt;
        _productRepository.AddAsync(product, cancellationToken);

    public Task UpdateAsync(Product product, CancellationToken cancellationToken = default) =&gt;
        _productRepository.UpdateAsync(product, cancellationToken);

    public Task DeleteAsync(int id, CancellationToken cancellationToken = default) =&gt;
        _productRepository.DeleteAsync(id, cancellationToken);
}
</code></pre>
<p><code>Controllers/ProductsController.cs</code></p>
<pre><code class="language-csharp">using Microsoft.AspNetCore.Mvc;
using RepositoryPattern.Web.Models;
using RepositoryPattern.Web.Services;

namespace RepositoryPattern.Web.Controllers;

public class ProductsController(ProductService productService) : Controller
{
    private readonly ProductService _productService = productService;

    public async Task&lt;IActionResult&gt; Index(CancellationToken cancellationToken)
    {
        const int lowStockThreshold = 5;
        var products = await _productService.GetProductsAsync(cancellationToken);
        var lowStock = await _productService.GetLowStockAsync(lowStockThreshold, cancellationToken);

        return View(new ProductListViewModel(products, lowStock, lowStockThreshold));
    }

    // remaining CRUD actions call through ProductService in the same way
}
</code></pre>
<p>The controller never reaches for <code>AppDbContext</code>. Every operation travels through the service, which keeps tests simple and makes future refactors easier.</p>
<h3>Dependency registration and seeding</h3>
<p>The last step is wiring everything up in <code>Program.cs</code>.</p>
<pre><code class="language-csharp">builder.Services.AddDbContext&lt;AppDbContext&gt;(options =&gt;
    options.UseInMemoryDatabase(&quot;ProductsDb&quot;));
builder.Services.AddScoped(typeof(IRepository&lt;&gt;), typeof(EfRepository&lt;&gt;));
builder.Services.AddScoped&lt;IProductRepository, ProductRepository&gt;();
builder.Services.AddScoped&lt;ProductService&gt;();
</code></pre>
<p>The sample also seeds three products so the list page shows data on first run.</p>
<p>Run the site with:</p>
<pre><code class="language-powershell">dotnet run --project RepositoryPattern.Web
</code></pre>
<h2>How ABP approaches the same idea</h2>
<p>ABP includes generic repositories by default (<code>IRepository&lt;TEntity, TKey&gt;</code>), so you often skip writing the implementation layer shown above. You inject the interface into an application service, call methods like <code>InsertAsync</code> or <code>CountAsync</code>, and ABP’s Unit of Work handles the transaction. When you need custom queries, you can still derive from <code>EfCoreRepository&lt;TEntity, TKey&gt;</code> and add them.</p>
<p>For more details, check out the official ABP documentation on repositories: https://abp.io/docs/latest/framework/architecture/domain-driven-design/repositories</p>
<h3>Closing note</h3>
<p>This setup keeps data access tidy without being heavy. Start with the generic repository, add small extensions per entity, pass everything through services, and register the dependencies once. Whether you hand-code it or let ABP supply the repository, the structure stays the same and your controllers remain clean.</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/3a1d55b6-a0fe-b929-3595-fad08909a45f" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/3a1d55b6-a0fe-b929-3595-fad08909a45f" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/implementing-unit-of-work-with-asp.net-core-lv4v2tyf</guid>
      <link>https://abp.io/community/posts/implementing-unit-of-work-with-asp.net-core-lv4v2tyf</link>
      <a10:author>
        <a10:name>m.aliozkaya</a10:name>
        <a10:uri>https://abp.io/community/members/m.aliozkaya</a10:uri>
      </a10:author>
      <category>entity-framework-core</category>
      <category>repository</category>
      <category>unit-of-work</category>
      <category>transactions</category>
      <category>aspnet-core</category>
      <title>Implementing Unit Of Work with ASP.NET Core</title>
      <description>This article demonstrates the Unit of Work pattern with Generic Repository in ASP.NET Core using a simple Product entity. The pattern ensures multiple database operations execute as a single transaction - either all succeed or all fail together, maintaining data consistency. The implementation includes IRepository/Repository for data operations and IUnitOfWork/UnitOfWork for transaction management. </description>
      <pubDate>Thu, 18 Sep 2025 12:16:10 Z</pubDate>
      <a10:updated>2026-10-22T22:22:34Z</a10:updated>
      <content:encoded><![CDATA[<h1>Unit of Work Pattern with Generic Repository in ASP.NET Core</h1>
<p>Picture this: You're building an e-commerce system and a customer places an order. You need to create the order record, update inventory, charge payment, and send a confirmation email. What happens if the payment succeeds but the inventory update fails? You're left with inconsistent data and an angry customer.</p>
<p>This is exactly where the Unit of Work pattern becomes invaluable. Instead of managing transactions manually across multiple repositories, this pattern coordinates all your data changes as a single, atomic operation.</p>
<h2>Understanding the Unit of Work Pattern</h2>
<p>The Unit of Work pattern maintains a list of objects affected by a business transaction and coordinates writing out changes while resolving concurrency problems. Think of it as your transaction coordinator that ensures all-or-nothing operations.</p>
<h3>The Problem: Scattered Transaction Management</h3>
<p>Without proper coordination, you often end up with code like this:</p>
<pre><code class="language-csharp">// Each repository manages its own context - risky!
await _productRepository.CreateAsync(product);
await _inventoryRepository.UpdateStockAsync(productId, -quantity);
await _orderRepository.CreateAsync(order);
</code></pre>
<p>This approach has several problems:</p>
<ul>
<li>Each operation might use a different database context</li>
<li>No automatic rollback if one operation fails</li>
<li>Manual transaction management becomes complex</li>
<li>Data consistency isn't guaranteed</li>
</ul>
<h3>The Solution: Coordinated Operations</h3>
<p>With Unit of Work, the same scenario becomes much cleaner:</p>
<pre><code class="language-csharp">await _unitOfWork.BeginTransactionAsync();

try
{
    var productRepo = _unitOfWork.Repository&lt;Product&gt;();
    var inventoryRepo = _unitOfWork.Repository&lt;Inventory&gt;();
    var orderRepo = _unitOfWork.Repository&lt;Order&gt;();

    await productRepo.AddAsync(product);
    await inventoryRepo.UpdateStockAsync(productId, -quantity);
    await orderRepo.AddAsync(order);

    await _unitOfWork.SaveChangesAsync();
    await _unitOfWork.CommitTransactionAsync();
}
catch
{
    await _unitOfWork.RollbackTransactionAsync();
    throw;
}
</code></pre>
<p>Now all operations either succeed together or fail together, guaranteeing data consistency.</p>
<h2>Sample Implementation</h2>
<blockquote>
<p>To keep the example short, I will only show the Product entity along with the implementation of the Generic Repository and Unit of Work. In this example, I will use Blazor and .NET 9.</p>
</blockquote>
<p>You can access the sample project here https://github.com/m-aliozkaya/UnitOfWorkDemo.</p>
<h3>1. Generic Repository Implementation</h3>
<p>The repository interface defines the contract for data operations. Here's what we're working with:</p>
<p><em>IRepository.cs</em> at <code>~/Data/Repositories</code></p>
<pre><code class="language-csharp">using System.Linq.Expressions;

namespace UOWDemo.Repositories;

public interface IRepository&lt;T&gt; where T : class
{
    Task&lt;T?&gt; GetByIdAsync(int id);
    Task&lt;IEnumerable&lt;T&gt;&gt; GetAllAsync();
    Task&lt;IEnumerable&lt;T&gt;&gt; FindAsync(Expression&lt;Func&lt;T, bool&gt;&gt; predicate);
    Task&lt;T?&gt; SingleOrDefaultAsync(Expression&lt;Func&lt;T, bool&gt;&gt; predicate);

    Task AddAsync(T entity);
    void Update(T entity);
    void Remove(T entity);
    void RemoveRange(IEnumerable&lt;T&gt; entities);

    Task&lt;bool&gt; ExistsAsync(int id);
    Task&lt;int&gt; CountAsync();
    Task&lt;int&gt; CountAsync(Expression&lt;Func&lt;T, bool&gt;&gt; predicate);
}
</code></pre>
<p><em>Repository.cs</em> at <code>~/Data/Repositories</code></p>
<pre><code class="language-csharp">using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using UOWDemo.Data;

namespace UOWDemo.Repositories;

public class Repository&lt;T&gt; : IRepository&lt;T&gt; where T : class
{
    protected readonly ApplicationDbContext _context;
    protected readonly DbSet&lt;T&gt; _dbSet;

    public Repository(ApplicationDbContext context)
    {
        _context = context;
        _dbSet = context.Set&lt;T&gt;();
    }

    public virtual async Task&lt;T?&gt; GetByIdAsync(int id)
    {
        return await _dbSet.FindAsync(id);
    }

    public virtual async Task&lt;IEnumerable&lt;T&gt;&gt; GetAllAsync()
    {
        return await _dbSet.ToListAsync();
    }

    public virtual async Task&lt;IEnumerable&lt;T&gt;&gt; FindAsync(Expression&lt;Func&lt;T, bool&gt;&gt; predicate)
    {
        return await _dbSet.Where(predicate).ToListAsync();
    }

    public virtual async Task&lt;T?&gt; SingleOrDefaultAsync(Expression&lt;Func&lt;T, bool&gt;&gt; predicate)
    {
        return await _dbSet.SingleOrDefaultAsync(predicate);
    }

    public virtual async Task AddAsync(T entity)
    {
        await _dbSet.AddAsync(entity);
    }

    public virtual void Update(T entity)
    {
        _dbSet.Update(entity);
    }

    public virtual void Remove(T entity)
    {
        _dbSet.Remove(entity);
    }

    public virtual void RemoveRange(IEnumerable&lt;T&gt; entities)
    {
        _dbSet.RemoveRange(entities);
    }

    public virtual async Task&lt;bool&gt; ExistsAsync(int id)
    {
        var entity = await _dbSet.FindAsync(id);
        return entity != null;
    }

    public virtual async Task&lt;int&gt; CountAsync()
    {
        return await _dbSet.CountAsync();
    }

    public virtual async Task&lt;int&gt; CountAsync(Expression&lt;Func&lt;T, bool&gt;&gt; predicate)
    {
        return await _dbSet.CountAsync(predicate);
    }
}
</code></pre>
<h3>2. Unit of Work Implementation</h3>
<p><em>IUnitOfWork.cs</em> at <code>~/Data/UnitOfWork</code></p>
<pre><code class="language-csharp">public interface IUnitOfWork : IDisposable, IAsyncDisposable
{
    IRepository&lt;T&gt; Repository&lt;T&gt;() where T : class;
    Task&lt;int&gt; SaveChangesAsync(); 
    Task BeginTransactionAsync();
    Task CommitTransactionAsync();
    Task RollbackTransactionAsync();
}
</code></pre>
<p><em>UnitOfWork.cs</em> at <code>~/Data/UnitOfWork</code></p>
<pre><code class="language-csharp">public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _context;
    private bool _disposed;
    private IDbContextTransaction? _currentTransaction;

    // ConcurrentDictionary for thread-safe repository caching
    private readonly ConcurrentDictionary&lt;Type, object&gt; _repositories = new();

    public UnitOfWork(ApplicationDbContext context)
    {
        _context = context;
    }

    public IRepository&lt;T&gt; Repository&lt;T&gt;() where T : class
    {
        return (IRepository&lt;T&gt;)_repositories.GetOrAdd(
            typeof(T),
            _ =&gt; new Repository&lt;T&gt;(_context)
        );
    }

    public int SaveChanges() =&gt; context.SaveChanges();
    public Task&lt;int&gt; SaveChangesAsync(CancellationToken cancellationToken = default) =&gt;
        context.SaveChangesAsync(cancellationToken);

    public async Task BeginTransactionAsync()
    {
        if (_currentTransaction != null)
            return;

        _currentTransaction = await _context.Database.BeginTransactionAsync();
    }

    public async Task CommitTransactionAsync()
    {
        if (_currentTransaction == null)
            return;

        await _currentTransaction.CommitAsync();
        await _currentTransaction.DisposeAsync();
        _currentTransaction = null;
    }

    public async Task RollbackTransactionAsync()
    {
        if (_currentTransaction == null)
            return;

        await _currentTransaction.RollbackAsync();
        await _currentTransaction.DisposeAsync();
        _currentTransaction = null;
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            _context.Dispose();
            _currentTransaction?.Dispose();
            _disposed = true;
        }
        GC.SuppressFinalize(this);
    }

    public async ValueTask DisposeAsync()
    {
        if (!_disposed)
        {
            await _context.DisposeAsync();
            if (_currentTransaction != null)
            {
                await _currentTransaction.DisposeAsync();
            }
            _disposed = true;
        }
        GC.SuppressFinalize(this);
    }
}
</code></pre>
<h3>3. Configure Dependency Injection</h3>
<p>Register the services in your <code>Program.cs</code>:</p>
<pre><code class="language-csharp">builder.Services.AddScoped&lt;IUnitOfWork, UnitOfWork&gt;();
</code></pre>
<h3>4. Defining and Registering Entity to DbContext</h3>
<p>First, let's define a simple <code>Product</code> entity:</p>
<p><em>Product.cs</em> at <code>~/Data/Entities</code></p>
<pre><code class="language-csharp">using System.ComponentModel.DataAnnotations;

namespace UOWDemo.Models;

public class Product
{
    public int Id { get; set; }

    [Required]
    [MaxLength(100)]
    public string Name { get; set; } = string.Empty;

    [MaxLength(500)]
    public string? Description { get; set; }

    [Required]
    [Range(0.01, double.MaxValue)]
    public decimal Price { get; set; }

    [Required]
    [Range(0, int.MaxValue)]
    public int Stock { get; set; }

    public DateTime CreatedDate { get; set; } = DateTime.UtcNow;
}
</code></pre>
<ul>
<li>Go to your <code>DbContext</code> and implement following code.</li>
</ul>
<pre><code class="language-csharp">    public DbSet&lt;Product&gt; Products { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity&lt;Product&gt;(entity =&gt;
        {
            entity.HasKey(e =&gt; e.Id);
            entity.Property(e =&gt; e.Price).HasPrecision(18, 2);
            entity.Property(e =&gt; e.CreatedDate).HasDefaultValueSql(&quot;GETUTCDATE()&quot;);
        });

        modelBuilder.Entity&lt;Product&gt;().HasData(
            new Product { Id = 1, Name = &quot;Laptop&quot;, Description = &quot;High-performance laptop&quot;, Price = 1299.99m, Stock = 15, CreatedDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) },
            new Product { Id = 2, Name = &quot;Mouse&quot;, Description = &quot;Wireless gaming mouse&quot;, Price = 79.99m, Stock = 50, CreatedDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) },
            new Product { Id = 3, Name = &quot;Keyboard&quot;, Description = &quot;Mechanical keyboard&quot;, Price = 149.99m, Stock = 25, CreatedDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) }
        );
    }
</code></pre>
<h3>5. Implement the UI</h3>
<p><em>ProductService.cs</em> at <code>~/Services</code></p>
<pre><code class="language-csharp">using System.Text.Json;
using UOWDemo.Models;

namespace UOWDemo.Services;

public class ProductService
{
    private readonly HttpClient _httpClient;

    public ProductService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task&lt;List&lt;Product&gt;&gt; GetAllProductsAsync()
    {
        var response = await _httpClient.GetAsync(&quot;/api/products&quot;);
        response.EnsureSuccessStatusCode();
        var json = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize&lt;List&lt;Product&gt;&gt;(json, new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        }) ?? new List&lt;Product&gt;();
    }
        
    public async Task CreateTwoProductsWithUowAsync(Product product1, Product product2)
    {
        var request = new TwoProductsRequest { Product1 = product1, Product2 = product2 };
        var json = JsonSerializer.Serialize(request);
        var content = new StringContent(json, System.Text.Encoding.UTF8, &quot;application/json&quot;);

        var response = await _httpClient.PostAsync(&quot;/api/products/two-products-with-uow&quot;, content);
        response.EnsureSuccessStatusCode();
    }

    public async Task CreateTwoProductsWithoutUowAsync(Product product1, Product product2)
    {
        var request = new TwoProductsRequest { Product1 = product1, Product2 = product2 };
        var json = JsonSerializer.Serialize(request);
        var content = new StringContent(json, System.Text.Encoding.UTF8, &quot;application/json&quot;);

        var response = await _httpClient.PostAsync(&quot;/api/products/two-products-without-uow&quot;, content);
        response.EnsureSuccessStatusCode();
    }

    public async Task DeleteProductAsync(int id)
    {
        var response = await _httpClient.DeleteAsync($&quot;/api/products/{id}&quot;);
        response.EnsureSuccessStatusCode();
    }
}
</code></pre>
<p><em>Products.razor</em> at <code>~/Components/Pages</code></p>
<pre><code class="language-csharp">@page &quot;/products&quot;
@using UOWDemo.Models
@using UOWDemo.Services
@inject ProductService ProductService
@inject IJSRuntime JSRuntime
@rendermode InteractiveServer

&lt;PageTitle&gt;Products&lt;/PageTitle&gt;

&lt;div class=&quot;container&quot;&gt;
    &lt;div class=&quot;row&quot;&gt;
        &lt;div class=&quot;col-12&quot;&gt;
            &lt;h1&gt;Product Management&lt;/h1&gt;
            &lt;p&gt;This page demonstrates Unit of Work transaction patterns with bulk operations.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&quot;row mb-3&quot;&gt;
        &lt;div class=&quot;col-12&quot;&gt;
            &lt;button class=&quot;btn btn-success me-2&quot; @onclick=&quot;ShowTwoProductsForm&quot;&gt;
                &lt;i class=&quot;bi bi-plus-circle&quot;&gt;&lt;/i&gt; Add Two Products Demo
            &lt;/button&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    @if (isLoading)
    {
        &lt;div class=&quot;text-center&quot;&gt;
            &lt;div class=&quot;spinner-border&quot; role=&quot;status&quot;&gt;
                &lt;span class=&quot;visually-hidden&quot;&gt;Loading...&lt;/span&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    }
    else
    {
        &lt;div class=&quot;row&quot;&gt;
            &lt;div class=&quot;col-12&quot;&gt;
                &lt;div class=&quot;table-responsive&quot;&gt;
                    &lt;table class=&quot;table table-striped table-hover&quot;&gt;
                        &lt;thead class=&quot;table-dark&quot;&gt;
                            &lt;tr&gt;
                                &lt;th&gt;ID&lt;/th&gt;
                                &lt;th&gt;Name&lt;/th&gt;
                                &lt;th&gt;Description&lt;/th&gt;
                                &lt;th&gt;Price&lt;/th&gt;
                                &lt;th&gt;Stock&lt;/th&gt;
                                &lt;th&gt;Actions&lt;/th&gt;
                            &lt;/tr&gt;
                        &lt;/thead&gt;
                        &lt;tbody&gt;
                            @foreach (var product in products)
                            {
                                &lt;tr&gt;
                                    &lt;td&gt;@product.Id&lt;/td&gt;
                                    &lt;td&gt;@product.Name&lt;/td&gt;
                                    &lt;td&gt;@product.Description&lt;/td&gt;
                                    &lt;td&gt;$@product.Price.ToString(&quot;F2&quot;)&lt;/td&gt;
                                    &lt;td&gt;@product.Stock&lt;/td&gt;
                                    &lt;td&gt;
                                        &lt;button class=&quot;btn btn-sm btn-outline-danger&quot; @onclick=&quot;() =&gt; DeleteProduct(product.Id)&quot;&gt;
                                            &lt;i class=&quot;bi bi-trash&quot;&gt;&lt;/i&gt; Delete
                                        &lt;/button&gt;
                                    &lt;/td&gt;
                                &lt;/tr&gt;
                            }
                        &lt;/tbody&gt;
                    &lt;/table&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    }
&lt;/div&gt;


@if (showTwoProductsModal)
{
    &lt;div class=&quot;modal fade show d-block&quot; tabindex=&quot;-1&quot; style=&quot;background-color: rgba(0,0,0,0.5);&quot;&gt;
        &lt;div class=&quot;modal-dialog modal-lg&quot;&gt;
            &lt;div class=&quot;modal-content&quot;&gt;
                &lt;div class=&quot;modal-header&quot;&gt;
                    &lt;h5 class=&quot;modal-title&quot;&gt;Two Products Demo - Unit of Work vs No Unit of Work&lt;/h5&gt;
                    &lt;button type=&quot;button&quot; class=&quot;btn-close&quot; @onclick=&quot;HideTwoProductsModal&quot;&gt;&lt;/button&gt;
                &lt;/div&gt;
                &lt;div class=&quot;modal-body&quot;&gt;
                    &lt;div class=&quot;row&quot;&gt;
                        &lt;div class=&quot;col-md-6&quot;&gt;
                            &lt;div class=&quot;card&quot;&gt;
                                &lt;div class=&quot;card-header&quot;&gt;
                                    &lt;h6 class=&quot;card-title&quot;&gt;Product 1&lt;/h6&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;card-body&quot;&gt;
                                    &lt;div class=&quot;mb-3&quot;&gt;
                                        &lt;label class=&quot;form-label&quot;&gt;Name&lt;/label&gt;
                                        &lt;input type=&quot;text&quot; class=&quot;form-control&quot; @bind=&quot;product1.Name&quot; /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;mb-3&quot;&gt;
                                        &lt;label class=&quot;form-label&quot;&gt;Description&lt;/label&gt;
                                        &lt;textarea class=&quot;form-control&quot; @bind=&quot;product1.Description&quot;&gt;&lt;/textarea&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;mb-3&quot;&gt;
                                        &lt;label class=&quot;form-label&quot;&gt;Price&lt;/label&gt;
                                        &lt;input type=&quot;number&quot; step=&quot;0.01&quot; class=&quot;form-control&quot; @bind=&quot;product1.Price&quot; /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;mb-3&quot;&gt;
                                        &lt;label class=&quot;form-label&quot;&gt;Stock&lt;/label&gt;
                                        &lt;input type=&quot;number&quot; class=&quot;form-control&quot; @bind=&quot;product1.Stock&quot; /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                        &lt;div class=&quot;col-md-6&quot;&gt;
                            &lt;div class=&quot;card&quot;&gt;
                                &lt;div class=&quot;card-header&quot;&gt;
                                    &lt;h6 class=&quot;card-title&quot;&gt;Product 2&lt;/h6&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;card-body&quot;&gt;
                                    &lt;div class=&quot;mb-3&quot;&gt;
                                        &lt;label class=&quot;form-label&quot;&gt;Name&lt;/label&gt;
                                        &lt;input type=&quot;text&quot; class=&quot;form-control&quot; @bind=&quot;product2.Name&quot; /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;mb-3&quot;&gt;
                                        &lt;label class=&quot;form-label&quot;&gt;Description&lt;/label&gt;
                                        &lt;textarea class=&quot;form-control&quot; @bind=&quot;product2.Description&quot;&gt;&lt;/textarea&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;mb-3&quot;&gt;
                                        &lt;label class=&quot;form-label&quot;&gt;Price&lt;/label&gt;
                                        &lt;input type=&quot;number&quot; step=&quot;0.01&quot; class=&quot;form-control&quot; @bind=&quot;product2.Price&quot; /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;mb-3&quot;&gt;
                                        &lt;label class=&quot;form-label&quot;&gt;Stock&lt;/label&gt;
                                        &lt;input type=&quot;number&quot; class=&quot;form-control&quot; @bind=&quot;product2.Stock&quot; /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                    &lt;div class=&quot;alert alert-info mt-3&quot;&gt;
                        &lt;strong&gt;Demo Purpose:&lt;/strong&gt; Check the console logs to see the difference in SQL transactions.
                        &lt;ul&gt;
                            &lt;li&gt;&lt;strong&gt;With Unit of Work:&lt;/strong&gt; Both products saved in a single transaction&lt;/li&gt;
                            &lt;li&gt;&lt;strong&gt;Without Unit of Work:&lt;/strong&gt; Each product saved in separate transactions&lt;/li&gt;
                        &lt;/ul&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
                &lt;div class=&quot;modal-footer d-flex justify-content-between&quot;&gt;
                    &lt;button type=&quot;button&quot; class=&quot;btn btn-success flex-fill me-2&quot; @onclick=&quot;SaveTwoProductsWithUow&quot;&gt;
                        Save with Unit of Work (Single Transaction)
                    &lt;/button&gt;
                    &lt;button type=&quot;button&quot; class=&quot;btn btn-warning flex-fill ms-2&quot; @onclick=&quot;SaveTwoProductsWithoutUow&quot;&gt;
                        Save without Unit of Work (Separate Transactions)
                    &lt;/button&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
}

@code {
    private List&lt;Product&gt; products = new();
    private bool isLoading = true;

    private bool showTwoProductsModal = false;
    private Product product1 = new();
    private Product product2 = new();

    protected override async Task OnInitializedAsync()
    {
        await LoadProducts();
    }

    private async Task LoadProducts()
    {
        try
        {
            isLoading = true;
            products = await ProductService.GetAllProductsAsync();
        }
        catch (Exception ex)
        {
            await JSRuntime.InvokeVoidAsync(&quot;alert&quot;, $&quot;Error loading products: {ex.Message}&quot;);
        }
        finally
        {
            isLoading = false;
        }
    }


    private void ShowTwoProductsForm()
    {
        product1 = new Product { Name = &quot;Product 1&quot;, Description = &quot;First product description&quot;, Price = 10.00m, Stock = 100 };
        product2 = new Product { Name = &quot;Product 2&quot;, Description = &quot;Second product description&quot;, Price = 20.00m, Stock = 50 };
        showTwoProductsModal = true;
    }

    private void HideTwoProductsModal()
    {
        showTwoProductsModal = false;
        product1 = new Product();
        product2 = new Product();
    }

    private async Task SaveTwoProductsWithUow()
    {
        try
        {
            await ProductService.CreateTwoProductsWithUowAsync(product1, product2);
            await JSRuntime.InvokeVoidAsync(&quot;alert&quot;, &quot;Two products saved with Unit of Work! Check console logs to see single transaction.&quot;);
            await LoadProducts();
            HideTwoProductsModal();
        }
        catch (Exception ex)
        {
            await JSRuntime.InvokeVoidAsync(&quot;alert&quot;, $&quot;Error saving products with UoW: {ex.Message}&quot;);
        }
    }

    private async Task SaveTwoProductsWithoutUow()
    {
        try
        {
            await ProductService.CreateTwoProductsWithoutUowAsync(product1, product2);
            await JSRuntime.InvokeVoidAsync(&quot;alert&quot;, &quot;Two products saved without Unit of Work! Check console logs to see separate transactions.&quot;);
            await LoadProducts();
            HideTwoProductsModal();
        }
        catch (Exception ex)
        {
            await JSRuntime.InvokeVoidAsync(&quot;alert&quot;, $&quot;Error saving products without UoW: {ex.Message}&quot;);
        }
    }

    private async Task DeleteProduct(int id)
    {
        if (await JSRuntime.InvokeAsync&lt;bool&gt;(&quot;confirm&quot;, &quot;Are you sure you want to delete this product?&quot;))
        {
            try
            {
                await ProductService.DeleteProductAsync(id);
                await LoadProducts();
            }
            catch (Exception ex)
            {
                await JSRuntime.InvokeVoidAsync(&quot;alert&quot;, $&quot;Error deleting product: {ex.Message}&quot;);
            }
        }
    }
}
</code></pre>
<p>UI Preview</p>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2025-09-17-Unit-of-Work-with-Aspnetcore/img-1.png" alt="UI Preview" /></p>
<p><em>ProductsController.cs</em> at <code>~/Controllers</code></p>
<pre><code class="language-csharp">using Microsoft.AspNetCore.Mvc;
using UOWDemo.Data;
using UOWDemo.Models;
using UOWDemo.UnitOfWork;

namespace UOWDemo.Controllers;

[ApiController]
[Route(&quot;api/[controller]&quot;)]
public class ProductsController : ControllerBase
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly ApplicationDbContext _context;

    public ProductsController(IUnitOfWork unitOfWork, ApplicationDbContext context)
    {
        _unitOfWork = unitOfWork;
        _context = context;
    }

    [HttpGet]
    public async Task&lt;IActionResult&gt; GetProducts()
    {
        var repository = _unitOfWork.Repository&lt;Product&gt;();
        var products = await repository.GetAllAsync();
        return Ok(products);
    }


    [HttpPost(&quot;two-products-with-uow&quot;)]
    public async Task&lt;IActionResult&gt; CreateTwoProductsWithUow(TwoProductsRequest request)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);

        await _unitOfWork.BeginTransactionAsync();

        try
        {
            var repository = _unitOfWork.Repository&lt;Product&gt;();
            await repository.AddAsync(request.Product1);
            await repository.AddAsync(request.Product2);
            await _unitOfWork.SaveChangesAsync();
            await _unitOfWork.CommitTransactionAsync();

            return Ok(new { message = &quot;Two products created with Unit of Work (single transaction)&quot;,
                products = new[] { request.Product1, request.Product2 } });
        }
        catch
        {
            await _unitOfWork.RollbackTransactionAsync();
            throw;
        }
    }

    [HttpPost(&quot;two-products-without-uow&quot;)]
    public async Task&lt;IActionResult&gt; CreateTwoProductsWithoutUow(TwoProductsRequest request)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);

        // First product - separate transaction
        await _context.Products.AddAsync(request.Product1);
        await _context.SaveChangesAsync();

        // Second product - separate transaction
        await _context.Products.AddAsync(request.Product2);
        await _context.SaveChangesAsync();

        return Ok(new { message = &quot;Two products created without Unit of Work (separate transactions)&quot;,
            products = new[] { request.Product1, request.Product2 } });
    }

    [HttpDelete(&quot;{id}&quot;)]
    public async Task&lt;IActionResult&gt; DeleteProduct(int id)
    {
        var repository = _unitOfWork.Repository&lt;Product&gt;();
        var product = await repository.GetByIdAsync(id);

        if (product == null)
            return NotFound();

        repository.Remove(product);
        await _unitOfWork.SaveChangesAsync();

        return NoContent();
    }
}
</code></pre>
<p>The code base should be shown like this.</p>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2025-09-17-Unit-of-Work-with-Aspnetcore/img-2.png" alt="Code base" /></p>
<h3>6. Seeing the Difference: Transaction Logging</h3>
<p>To see the transaction behavior in action, configure Entity Framework logging in <code>appsettings.Development.json</code>:</p>
<pre><code class="language-json">{
  &quot;Logging&quot;: {
    &quot;LogLevel&quot;: {
      &quot;Default&quot;: &quot;Information&quot;,
      &quot;Microsoft.AspNetCore&quot;: &quot;Warning&quot;,
      &quot;Microsoft.EntityFrameworkCore.Database.Command&quot;: &quot;Information&quot;,
      &quot;Microsoft.EntityFrameworkCore.Database.Transaction&quot;: &quot;Information&quot;
    }
  }
}
</code></pre>
<p>Now when you run the demo, the console will show:</p>
<p><strong>With Unit of Work - One Transaction:</strong></p>
<pre><code>BEGIN TRANSACTION
INSERT INTO [Products] ([Name], [Description], [Price], [Stock]) VALUES ('Product 1', ...)
INSERT INTO [Products] ([Name], [Description], [Price], [Stock]) VALUES ('Product 2', ...)
COMMIT TRANSACTION
</code></pre>
<p><strong>Without Unit of Work - Two Separate Transactions:</strong></p>
<pre><code>BEGIN TRANSACTION
INSERT INTO [Products] ([Name], [Description], [Price], [Stock]) VALUES ('Product 1', ...)
COMMIT TRANSACTION

BEGIN TRANSACTION
INSERT INTO [Products] ([Name], [Description], [Price], [Stock]) VALUES ('Product 2', ...)
COMMIT TRANSACTION
</code></pre>
<h2>What If?</h2>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2025-09-17-Unit-of-Work-with-Aspnetcore/img-3.png" alt="What if" /></p>
<p>What if we were using the ABP Framework instead of manually implementing the Unit of Work and Generic Repository? Well, most of the heavy lifting we did in this example would be handled automatically. ABP provides built-in support for Unit of Work, transactions, and repository patterns, allowing you to focus on business logic rather than plumbing code.</p>
<h3>Key Advantages with ABP</h3>
<p>Automatic Transaction Management: Every application service method runs within a transaction by default. If an exception occurs, changes are automatically rolled back.</p>
<ul>
<li><p>[UnitOfWork] Attribute: You can simply annotate a method with [UnitOfWork] to ensure all repository operations within it run in a single transaction.</p>
</li>
<li><p>Automatic SaveChanges: You don’t need to call SaveChanges() manually; ABP takes care of persisting changes at the end of a Unit of Work.</p>
</li>
<li><p>Configurable Transaction Options: Transaction isolation levels and timeouts can be easily configured, helping with performance and data consistency.</p>
</li>
<li><p>Event-Based Completion: After a successful transaction, related domain events can be triggered automatically—for example, sending a confirmation email when an entity is created.</p>
</li>
</ul>
<p>And many of them. If you interested in check this document. https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work</p>
<p>📌 Example:</p>
<pre><code class="language-csharp">[UnitOfWork]
public void CreatePerson(CreatePersonInput input)
{
    var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
    _personRepository.Insert(person);
    _statisticsRepository.IncrementPeopleCount();
}
</code></pre>
<p>In this example, both repository operations execute within the same transaction. ABP handles the commit automatically, so either all changes succeed or none are applied.</p>
<blockquote>
<p>Takeaway: With ABP, developers don’t need to manually implement Unit of Work or manage transactions. This reduces boilerplate code, ensures consistency, and lets you focus on the domain logic.</p>
</blockquote>
<h2>Conclusion</h2>
<p>The Unit of Work pattern shines in scenarios where multiple operations must succeed or fail together. By centralizing transaction management and repository access, it reduces complexity and ensures consistency.</p>
<p>This demo kept things simple with a single Product entity, but the same approach scales to more complex domains. Whether you’re building an e-commerce app, a financial system, or any data-heavy application, adopting Unit of Work with a Generic Repository can make your codebase cleaner, safer, and easier to maintain.</p>
<p>Feel free to clone the sample project, experiment with it, and adapt the pattern to your own needs. 🚀</p>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/3a1c6eed-c072-4aa1-2fa2-e00a6a43c1f9" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/3a1c6eed-c072-4aa1-2fa2-e00a6a43c1f9" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/hierarchyid-support-in-entity-framework-core-k9n95ufu</guid>
      <link>https://abp.io/community/posts/hierarchyid-support-in-entity-framework-core-k9n95ufu</link>
      <a10:author>
        <a10:name>m.aliozkaya</a10:name>
        <a10:uri>https://abp.io/community/members/m.aliozkaya</a10:uri>
      </a10:author>
      <category>entity-framework-core</category>
      <category>net8</category>
      <category>efcore8</category>
      <title>HierarchyId Support in Entity Framework Core</title>
      <description>Entity Framework Core supports HierarchyId for efficiently handling hierarchical data in SQL Server.  Install the package and configure your project with HierarchyId methods to simplify hierarchical data management in your application.
</description>
      <pubDate>Wed, 08 Nov 2023 07:51:37 Z</pubDate>
      <a10:updated>2026-10-22T18:53:55Z</a10:updated>
      <content:encoded><![CDATA[<h1>HierarchyId Support in Entity Framework Core</h1>
<p>Entity Framework Core has official support for HierarchyId, which allows you to store and query hierarchical data in SQL Server databases. Hierarchical data is a common data structure found in many applications. Whether you are dealing with organizational structures, product categories, or threaded discussions, handling hierarchies efficiently is crucial. In this blog post, we will explore how to manage hierarchical data using Entity Framework Core (EF Core) in combination with HierarchyId.</p>
<h2>How to use HierarchyId in EF Core</h2>
<p>To use HierarchyId in EF Core, you need to install the <a href="https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.SqlServer.HierarchyId/">Microsoft.EntityFrameworkCore.SqlServer.HierarchyId</a> NuGet package. This package contains query and update support for HierarchyId.</p>
<p>To configure EF Core to use HierarchyId, you need to call the <code>UseHierarchyId</code> method when configuring the SQL Server provider.</p>
<pre><code class="language-csharp">options.UseSqlServer(
    connectionString,
    x =&gt; x.UseHierarchyId());
</code></pre>
<h2>Modeling Hierarchies</h2>
<p>The <code>HierarchyId</code> type can be used for properties of an entity type. For example, assume we want to model the personnel in an organization. Each person has a name and a manager. The manager is also a person. We can model this using the following entity type:</p>
<pre><code class="language-csharp">public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public HierarchyId Manager { get; set; }
}
</code></pre>
<p>Each person can be traced from the patriarch down the tree using its <code>Manager</code> property. SQL Server uses a compact binary format for these paths, but it is common to parse to and from a human-readable string representation when working with code. In this representation, the position at each level is separated by a <code>/</code> character.</p>
<p><img src="https://raw.githubusercontent.com/abpframework/abp/dev/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/hierarchy-tree.png" alt="Hierarchy Tree" /></p>
<h2>Querying Hierarchies</h2>
<p>HierarchyId exposes several methods that can be used in LINQ queries.</p>
<ul>
<li><p><code>GetAncestor(int level)</code> returns the ancestor at the specified level.</p>
</li>
<li><p><code>GetDescendant(HierarchyId? child1, HierarchyId? child2)</code> returns the descendant at the specified level.</p>
</li>
<li><p><code>GetLevel()</code> returns the level of the node in the hierarchy.</p>
</li>
<li><p><code>GetReparentedValue(HierarchyId? oldRoot, HierarchyId? newRoot)</code> returns the node with a new parent.</p>
</li>
<li><p><code>IsDescendantOf(HierarchyId? parent)</code> returns whether the node is a descendant of the specified node.</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="language-csharp">// Get entities at a given level in the tree
var generation = await context.Persons.Where(x =&gt; x.Manager.GetLevel() == level).ToListAsync();

// Get the direct ancestor of an entity
var parent = await context.Persons.Where(x =&gt; x.Manager.GetAncestor(1) == manager).SingleAsync();
</code></pre>
<p>For example, your company may want to change the manager of an organizational unit. To do this, you can use the <code>GetReparentedValue</code> method to update the manager for all descendants of the organizational unit.</p>
<pre><code class="language-csharp">var newManager = await context.Persons.SingleAsync(x =&gt; x.Id == newManagerId);

var descendants = await context.Persons
    .Where(x =&gt; x.Manager.IsDescendantOf(oldManager))
    .ToListAsync();

foreach (var descendant in descendants)
{
    descendant.Manager = descendant.Manager.GetReparentedValue(oldManager.Manager, newManager.Manager);
}

await context.SaveChangesAsync();
</code></pre>
<h2>References</h2>
<p>For more information about hierarchy id, see the following resource:</p>
<ul>
<li><a href="https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#hierarchyid-in-net-and-ef-core">https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#hierarchyid-in-net-and-ef-core</a></li>
</ul>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/3a0ec017-2b2a-1d3a-da37-aaad56cacbd1" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/3a0ec017-2b2a-1d3a-da37-aaad56cacbd1" medium="image" />
    </item>
    <item>
      <guid isPermaLink="true">https://abp.io/community/posts/grpc-health-checks-with-asp.net-core-7.0-kwnjkun1</guid>
      <link>https://abp.io/community/posts/grpc-health-checks-with-asp.net-core-7.0-kwnjkun1</link>
      <a10:author>
        <a10:name>m.aliozkaya</a10:name>
        <a10:uri>https://abp.io/community/members/m.aliozkaya</a10:uri>
      </a10:author>
      <category>dotnet7</category>
      <category>dotnet</category>
      <category>gRPC</category>
      <title>gRPC Health Checks with ASP.NET Core 7.0</title>
      <description>This article will show how to use gRPC health checks with the ABP Framework.</description>
      <pubDate>Mon, 28 Nov 2022 12:52:32 Z</pubDate>
      <a10:updated>2026-10-22T18:56:47Z</a10:updated>
      <content:encoded><![CDATA[<h1>gRPC - Health Checks</h1>
<p>In this article we will show how to use gRPC health checks with the ABP Framework.</p>
<h2>Health Checks</h2>
<p>ASP.NET Core 7 supports gRPC health checks. Health Checks allow us to determine the overall health and availability of our application infrastructure. They are exposed as HTTP endpoints and can be configured to provide information for various monitoring scenarios, such as the response time and memory usage of our application, or whether our application can communicate with our database provider.</p>
<h3>gRPC Health Checks</h3>
<p>The <a href="https://github.com/grpc/grpc/blob/master/doc/health-checking.md">gRPC health checking protocol</a> is a standard for reporting the health of gRPC server apps. An app exposes health checks as a gRPC service. They are typically used with an external monitoring service to check the status of an app.</p>
<h3>Grpc.AspNetCore.HealthChecks</h3>
<p>ASP.NET Core supports the gRPC health checking protocol with the <a href="https://www.nuget.org/packages/Grpc.AspNetCore.HealthChecks">Grpc.AspNetCore.HealthChecks</a> package. Results from .NET health checks are reported to callers.</p>
<h2>Using gRPC Health Checks with the ABP Framework</h2>
<p>In this article, I'm assuming you've used gRPC with ABP before. If you are still having problems with this, it may be good for you to review this article.
https://community.abp.io/posts/using-grpc-with-the-abp-framework-2dgaxzw3</p>
<h3>Set up gRPC Health Checks</h3>
<p>In this solution, <code>*.HttpApi.Host</code> is the project that configures and runs the server-side application. So, we will make changes in that project.</p>
<ul>
<li>Add the <code>Grpc.AspNetCore.HealthChecks</code> package to your project.</li>
</ul>
<pre><code class="language-bash">dotnet add package Grpc.AspNetCore.HealthChecks
</code></pre>
<ul>
<li><code>AddGrpcHealthChecks</code> to register services that enable health checks.</li>
</ul>
<pre><code class="language-csharp">public override void ConfigureServices(ServiceConfigurationContext context)
{
    // Other configurations...

    context.Services.AddGrpcHealthChecks()
        .AddCheck(&quot;SampleHealthCheck&quot;, () =&gt; HealthCheckResult.Healthy());
}
</code></pre>
<ul>
<li><code>MapGrpcHealthChecksService</code> to add a health check service endpoint.</li>
</ul>
<pre><code class="language-csharp">public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
    // Other middlewares...

    app.UseConfiguredEndpoints(builder =&gt;
    {
        builder.MapGrpcHealthChecksService();
    });
}
</code></pre>
<h3>Calling Health Checks From a Client</h3>
<p>Now that our server is configured for gRPC health checks, we can test it by creating a basic console client.</p>
<pre><code class="language-csharp">var channel = GrpcChannel.ForAddress(&quot;https://localhost:44357&quot;);
var client = new Health.HealthClient(channel);

var response = await client.CheckAsync(new HealthCheckRequest());
var status = response.Status;

Console.WriteLine($&quot;Health Status: {status}&quot;);
</code></pre>
<h2>References</h2>
<ul>
<li>https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-7.0?view=aspnetcore-7.0#grpc-health-checks-in-aspnet-core</li>
</ul>
]]></content:encoded>
      <media:thumbnail url="https://abp.io/api/posts/cover-picture-source/ffebfcd2-602c-eac5-85e4-3a07d078b11a" />
      <media:content url="https://abp.io/api/posts/cover-picture-source/ffebfcd2-602c-eac5-85e4-3a07d078b11a" medium="image" />
    </item>
  </channel>
</rss>