HomeNikola Knezevic

In this article

Banner

Getting Started with Entity Framework Extensions

11 Dec 2025
5 min

Special Thanks to Our Sponsors:

Sponsor Logo

EF Core is too slow? Discover how you can easily insert 14x faster (reducing saving time by 94%).

Boost your performance with our method within EF Core: Bulk Insert, update, delete and merge.

Thousands of satisfied customers have trusted our library since 2014.

👉 Learn more

Sponsor Newsletter

Entity Framework Core is an amazing ORM but when it comes to bulk operations, it can be slow.

Standard EF Core methods work fine for small to medium datasets but as the number of records grows, performance can degrade significantly.

In today's blog post, we'll explore Entity Framework Extensions, a powerful library that provides optimized bulk operations for EF Core, improving performance when working with large datasets.

EF Extensions

Entity Framework Extensions is a commercial library that extends EF Core with high-performance bulk operations.

It offers a wide range of bulk methods that execute operations directly in the database, bypassing EF Core's change tracker and generating optimized SQL statements.

To get started with Entity Framework Extensions, you need to install the NuGet package. You can do this via the NuGet Package Manager or by running the following command in the Package Manager Console:

bash
Install-Package Z.EntityFramework.Extensions

Once installed, you'll have access to extension methods on your DbContext that enable bulk operations.

For this blog post, we'll use a simple Product entity with a relationship to ProductReview:

csharp
public class Product
{
    public Guid Id { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? ModifiedAt { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public List<ProductReview> Reviews { get; set; }
}

public class ProductReview
{
    public Guid Id { get; set; }
    public Guid ProductId { get; set; }
    public Product Product { get; set; }
    public string Author { get; set; }
    public string Content { get; set; }
    public int Rating { get; set; }
    public DateTime CreatedAt { get; set; }
}

BulkInsert

BulkInsert is one of the most commonly used bulk operations. Instead of calling AddRange (or even worse, foreach Add) and SaveChanges, it generates optimized SQL that inserts all records in a single database roundtrip:

csharp
products.MapPost("/bulk-insert", async (ApplicationDbContext db) =>
{
    var items = ProductFactory.Generate(50).ToList();

    await db.BulkInsertAsync(items);
    return Results.Ok(new { inserted = items.Count });
});

BulkInsert doesn't use EF Core's change tracker, which means it won't automatically set navigation properties or track entities. However, it will populate primary keys if they're database-generated.

IncludeGraph

The IncludeGraph option tells BulkInsert to also insert related entities in the correct order:

csharp
products.MapPost("/bulk-insert-graph", async (ApplicationDbContext db) =>
{
    var items = ProductFactory.Generate(10).ToList();

    await db.BulkInsertAsync(items, options => { options.IncludeGraph = true; });
    return Results.Ok(new { inserted = items.Count });
});

With IncludeGraph enabled, if your Product entities have Reviews populated, BulkInsert will first insert the Products, then insert the Reviews.

BulkUpdate

BulkUpdate generates optimized SQL that updates all records in a single database operation, similar to BulkInsert:

csharp
products.MapPut("/bulk-update", async (ApplicationDbContext db) =>
{
    var items = await db.Products.Take(20).ToListAsync();
    foreach (var p in items)
    {
        p.Price += 10;
        p.ModifiedAt = DateTime.UtcNow;
    }
    
    await db.BulkUpdateAsync(items);
    return Results.Ok(new { updated = items.Count });
});

It identifies entities by their primary key and updates all modified properties.

BulkDelete

BulkDelete deletes multiple entities efficiently, much faster than calling RemoveRange (or even worse, foreach Remove) and SaveChanges for each entity:

csharp
products.MapDelete("/bulk-delete", async (ApplicationDbContext db) =>
{
    var items = await db.Products.Take(20).ToListAsync();
    
    await db.BulkDeleteAsync(items);
    return Results.Ok(new { deleted = items.Count });
});

BulkDelete identifies entities to delete by their primary key, so you only need to provide entities with their IDs populated.

BulkSaveChanges

BulkSaveChanges is a replacement for the standard SaveChanges method. It works with EF Core's change tracker but executes operations in bulk, useful when you want to keep change tracking while benefiting from bulk performance:

csharp
products.MapPost("/bulk-save-changes", async (ApplicationDbContext db) =>
{
    var items = ProductFactory.Generate(20).ToList();
    db.Products.AddRange(items);
    
    await db.BulkSaveChangesAsync();
    return Results.Ok(new { saved = items.Count });
});

It analyzes the change tracker and groups operations by type (inserts, updates, deletes), then executes them in bulk.

DeleteFromQuery

DeleteFromQuery efficiently deletes records by translating your LINQ filter into a single SQL DELETE operation without loading any entities into memory:

csharp
products.MapDelete("/delete-from-query", async (ApplicationDbContext db) =>
{
    var count = await db.Products
        .Where(p => p.Price < 10)
        .DeleteFromQueryAsync();
    
    return Results.Ok(new { deleted = count });
});

UpdateFromQuery

Similar to DeleteFromQuery, UpdateFromQuery performs batch updates by converting your LINQ filter and update expression into a single SQL UPDATE statement executed directly on the database, without loading any entities.

csharp
products.MapPut("/update-from-query", async (ApplicationDbContext db) =>
{
    var count = await db.Products
        .Where(p => p.Price < 200)
        .UpdateFromQueryAsync(p => new Product
        {
            Price = p.Price + 5,
            ModifiedAt = DateTime.UtcNow
        });
    
    return Results.Ok(new { updated = count });
});

BulkMerge

BulkMerge performs an upsert by using the primary key to insert new entities and update existing ones in a single operation:

csharp
products.MapPost("/bulk-merge", async (ApplicationDbContext db) =>
{
    var items = ProductFactory.Generate(30).ToList();
    
    await db.BulkMergeAsync(items);
    return Results.Ok(new { merged = items.Count });
});

BulkSynchronize

BulkSynchronize goes a step further than BulkMerge by also deleting entities that exist in the database but not in your collection, ensuring your database exactly matches your in-memory collection:

csharp
products.MapPost("/bulk-synchronize", async (ApplicationDbContext db) =>
{
    var items = ProductFactory.Generate(10).ToList();
    
    await db.BulkSynchronizeAsync(items);
    return Results.Ok(new { synchronized = items.Count });
});

WhereBulkContains

WhereBulkContains efficiently queries entities by a collection of IDs, generating optimized SQL instead of using multiple OR conditions or Contains with a large list:

csharp
products.MapGet("/bulk-contains", async (ApplicationDbContext db) =>
{
    var ids = await db.Products.Select(p => p.Id).Take(10).ToListAsync();
    var result = await db.Products.WhereBulkContains(ids).ToListAsync();
    
    return Results.Ok(result);
});

WhereBulkContains performs well even with thousands of IDs.

Conclusion

Entity Framework Extensions delivers highly optimized bulk operations that drastically improve performance and reduce memory usage when handling large datasets.

Bulk insert, update, delete, merge and synchronize are bypassing EF Core's change tracker and execute directly against database.

BulkSaveChanges works with EF Core's change tracker but executes operations in bulk.

DeleteFromQuery and UpdateFromQuery perform batch updates by converting LINQ into a single SQL statement executed directly on the database, without loading any entities.

WhereBulkContains efficiently queries entities by a collection of IDs.

Although it’s a commercial library, the speed and productivity gains often justify the investment.

If you want to check out examples I created, you can find the source code here:

Source Code

I hope you enjoyed it, subscribe and get a notification when a new blog is up!

Subscribe

Stay tuned for valuable insights every Thursday morning.