EntityFrameworkCore.ConcurrentChunking.DependencyInjection 2.0.0-prerelease.58

This is a prerelease version of EntityFrameworkCore.ConcurrentChunking.DependencyInjection.
dotnet add package EntityFrameworkCore.ConcurrentChunking.DependencyInjection --version 2.0.0-prerelease.58
                    
NuGet\Install-Package EntityFrameworkCore.ConcurrentChunking.DependencyInjection -Version 2.0.0-prerelease.58
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EntityFrameworkCore.ConcurrentChunking.DependencyInjection" Version="2.0.0-prerelease.58" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EntityFrameworkCore.ConcurrentChunking.DependencyInjection" Version="2.0.0-prerelease.58" />
                    
Directory.Packages.props
<PackageReference Include="EntityFrameworkCore.ConcurrentChunking.DependencyInjection" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EntityFrameworkCore.ConcurrentChunking.DependencyInjection --version 2.0.0-prerelease.58
                    
#r "nuget: EntityFrameworkCore.ConcurrentChunking.DependencyInjection, 2.0.0-prerelease.58"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EntityFrameworkCore.ConcurrentChunking.DependencyInjection@2.0.0-prerelease.58
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EntityFrameworkCore.ConcurrentChunking.DependencyInjection&version=2.0.0-prerelease.58&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=EntityFrameworkCore.ConcurrentChunking.DependencyInjection&version=2.0.0-prerelease.58&prerelease
                    
Install as a Cake Tool

EntityFrameworkCore.ConcurrentChunking

EntityFrameworkCore.ConcurrentChunking helps load large EF Core queries in parallel chunks with optional prefetching.

It is useful when a single large query takes too long to materialize before processing can begin, especially for workloads like:

  • data migration
  • reporting/export pipelines
  • batch processing and background jobs

Packages

  • EntityFrameworkCore.ConcurrentChunking - core loader API
  • EntityFrameworkCore.ConcurrentChunking.Linq - IQueryable<T> extension methods
  • EntityFrameworkCore.ConcurrentChunking.DependencyInjection - DI factory registration

Target frameworks

This repository currently targets:

  • net8.0
  • net9.0
  • net10.0

Installation

Install the package(s) you need:

dotnet add package EntityFrameworkCore.ConcurrentChunking
dotnet add package EntityFrameworkCore.ConcurrentChunking.Linq
dotnet add package EntityFrameworkCore.ConcurrentChunking.DependencyInjection

Quick start

1) Core loader (ChunkedEntityLoader<TDbContext, TEntity>)

using Microsoft.EntityFrameworkCore.ConcurrentChunking;

var loader = new ChunkedEntityLoader<TestDbContext, SimpleEntity>(
    dbContextFactory: () => new TestDbContext(),
    chunkSize: 100_000,
    maxConcurrentProducerCount: 3,
    maxPrefetchCount: 5,
    sourceQueryProvider: ctx => ctx.SimpleEntities
        .Include(e => e.RelatedEntity)
            .ThenInclude(e => e.AnotherRelatedEntity)
        .Include(e => e.RelatedEntity2)
        .AsNoTracking()
        .OrderBy(e => e.Id),
    options: ChunkedEntityLoaderOptions.PreserveChunkOrder
);

await foreach (var chunk in loader.LoadAsync(CancellationToken.None))
{
    foreach (var entity in chunk.Entities)
    {
        Console.WriteLine($"Entity ID: {entity.Id}");
    }
}

2) LINQ extension (LoadChunkedAsync)

LoadChunkedAsync requires an ordered query (OrderBy or OrderByDescending) to ensure stable chunking.

using Microsoft.EntityFrameworkCore.ConcurrentChunking;
using Microsoft.EntityFrameworkCore.ConcurrentChunking.Linq;

await using var ctx = new TestDbContext();

var chunks = ctx.TestEntities
    .Include(e => e.RelatedEntity)
        .ThenInclude(e => e.AnotherRelatedEntity)
    .Include(e => e.RelatedEntity2)
    .AsNoTracking()
    .OrderByDescending(e => e.Id)
    .LoadChunkedAsync(
        dbContextFactory: () => new TestDbContext(),
        chunkSize: 100_000,
        maxConcurrentProducerCount: 3,
        maxPrefetchCount: 5,
        options: ChunkedEntityLoaderOptions.PreserveChunkOrder);

await foreach (var chunk in chunks)
{
    foreach (var entity in chunk.Entities)
    {
        Console.WriteLine($"Entity ID: {entity.Id}");
    }
}

3) Dependency injection (IChunkedEntityLoaderFactory<TDbContext>)

Register the factory:

using Microsoft.EntityFrameworkCore.ConcurrentChunking.DependencyInjection;

services.AddChunkedEntityLoaderFactory();

Create and consume a loader from DI:

var factory = services.GetRequiredService<IChunkedEntityLoaderFactory<TestDbContext>>();

var loader = factory.Create(
    chunkSize: 100_000,
    maxConcurrentProducerCount: 3,
    maxPrefetchCount: 5,
    sourceQueryProvider: ctx => ctx.SimpleEntities
        .Include(e => e.RelatedEntity)
            .ThenInclude(e => e.AnotherRelatedEntity)
        .Include(e => e.RelatedEntity2)
        .AsNoTracking()
        .OrderBy(e => e.Id),
    options: ChunkedEntityLoaderOptions.PreserveChunkOrder
);

await foreach (var chunk in loader.LoadAsync(CancellationToken.None))
{
    foreach (var entity in chunk.Entities)
    {
        Console.WriteLine($"Entity ID: {entity.Id}");
    }
}

Advanced Usage

Chunk production callbacks

All loader entry points support two optional callbacks:

  • startProducingChunkCallback runs before a chunk query starts.
  • endProducingChunkCallback runs after the chunk query finishes.

Both callbacks receive ICallbackArgs<TContext>, which exposes:

  • ChunkIndex - the zero-based chunk number (read-only).
  • DbContext - the context used for the chunk. You can replace it in the start callback if you need a custom instance.
  • State - any value you want to pass from the start callback to the end callback.

This is useful for chunk-level logging, correlation IDs, metrics, or swapping in a specialized context for a specific chunk.

using Microsoft.EntityFrameworkCore.ConcurrentChunking;

var loader = new ChunkedEntityLoader<TestDbContext, SimpleEntity>(
    dbContextFactory: () => new TestDbContext(),
    chunkSize: 100_000,
    maxConcurrentProducerCount: 3,
    maxPrefetchCount: 5,
    sourceQueryProvider: ctx => ctx.SimpleEntities.OrderBy(e => e.Id),
    startProducingChunkCallback: args =>
    {
        args.State = Stopwatch.GetTimestamp();

        if (args.ChunkIndex == 0)
        {
            args.DbContext = new TestDbContext();
        }

        return Task.CompletedTask;
    },
    endProducingChunkCallback: args =>
    {
        if (args.State is long startedAt)
        {
            var elapsed = Stopwatch.GetElapsedTime(startedAt);
            Console.WriteLine($"Chunk {args.ChunkIndex} completed in {elapsed.TotalMilliseconds:n0} ms.");
        }

        return Task.CompletedTask;
    });

The same callbacks are available on LoadChunkedAsync(...) and on IChunkedEntityLoaderFactory<TDbContext>.Create(...), so you can use the same pattern whether you construct the loader directly, through LINQ, or from dependency injection.

Tuning guidance

  • chunkSize: larger chunks reduce round-trips but increase memory per chunk.
  • maxConcurrentProducerCount: controls how many chunk producers run in parallel.
  • maxPrefetchCount: limits queued chunk count and therefore memory pressure.
  • PreserveChunkOrder: keeps output ordered by chunk index; disable if out-of-order consumption is acceptable.

Start conservatively, measure DB pressure and memory usage, then increase settings incrementally.

Notes and caveats

  • Always use deterministic ordering in source queries.
  • Consider AsNoTracking() for read-only workloads.
  • Each producer uses its own DbContext instance; make sure your factory is safe for concurrent use, and see Advanced Usage for how the DbContext can be overwritten in the chunk production callbacks.

Build and test

From src:

dotnet restore
dotnet build -c Release
dotnet test -c Release

License

MIT. See LICENSE.txt.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0-prerelease.58 78 6/28/2026
1.1.0-prerelease.55 66 6/19/2026
1.1.0-prerelease.54 67 6/19/2026
1.1.0-prerelease.53 70 6/14/2026
1.0.0.47 133 5/22/2026
1.0.0 100 5/22/2026
1.0.0-prerelease.41 65 5/22/2026
1.0.0-prerelease.31 210 8/21/2025

1.0.0
           - Initial version