Skip to content

Fix EFCore.BulkExtensions compatibility: shadow _queryContextFactory in CustomQueryCompiler#200

Merged
PhenX merged 4 commits intomasterfrom
copilot/update-entityframeworkcore-projectables
Apr 11, 2026
Merged

Fix EFCore.BulkExtensions compatibility: shadow _queryContextFactory in CustomQueryCompiler#200
PhenX merged 4 commits intomasterfrom
copilot/update-entityframeworkcore-projectables

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 11, 2026

Projectables 3.x breaks EFCore.BulkExtensions' BatchDelete/BatchUpdate with TargetException: Non-static method requires a target on any project using both libraries.

Root Cause

BatchUtil.GetDbContext resolves the DbContext via this reflection chain:

object compiler = typeof(EntityQueryProvider)
    .GetField("_queryCompiler", NonPublic | Instance)
    .GetValue(provider);                                          // → CustomQueryCompiler

object factory = compiler.GetType()
    .GetField("_queryContextFactory", NonPublic | Instance)      // → null (!)
    ?.GetValue(compiler);

typeof(RelationalQueryContextFactory)
    .GetProperty("Dependencies", NonPublic | Instance)
    .GetValue(factory);                                           // throws: GetValue(null) on instance property

C# reflection does not surface private fields declared on a base class when GetField is called on a derived type. With plain EF Core, compiler.GetType() is QueryCompiler — the exact declaring type — so the lookup succeeds. With Projectables, compiler.GetType() is CustomQueryCompiler : QueryCompiler, so GetField("_queryContextFactory") returns null and the next GetValue(null) throws.

Fix

CustomQueryCompiler — shadow field

Added a private readonly IQueryContextFactory _queryContextFactory field directly on CustomQueryCompiler, initialized in the constructor with the same instance passed to the base. This makes the field visible to GetField when the lookup starts from the derived type:

// Shadows QueryCompiler._queryContextFactory so that libraries accessing it via
// obj.GetType().GetField("_queryContextFactory", NonPublic | Instance) can find it.
#pragma warning disable IDE0052
private readonly IQueryContextFactory _queryContextFactory;
#pragma warning restore IDE0052

New VendorTests project

Added tests/EntityFrameworkCore.Projectables.VendorTests (net8.0, EFCore.BulkExtensions 8.0.4 + SQLite in-memory) with tests that:

  • Assert BatchUtil.GetDbContext(query) does not throw and returns the expected context
  • Assert BatchDeleteAsync / BatchUpdateAsync do not throw a TargetException

Copilot AI changed the title [WIP] Update EntityFrameworkCore.Projectables to fix bulk operation issues Fix EFCore.BulkExtensions compatibility: shadow _queryContextFactory in CustomQueryCompiler Apr 11, 2026
Copilot AI requested a review from PhenX April 11, 2026 09:13
@PhenX PhenX requested a review from Copilot April 11, 2026 09:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a regression in the runtime integration layer where Projectables’ CustomQueryCompiler : QueryCompiler breaks EFCore.BulkExtensions’ reflection-based DbContext discovery, causing BatchDelete/BatchUpdate to throw TargetException.

Changes:

  • Shadow QueryCompiler’s private _queryContextFactory field on CustomQueryCompiler so GetField(...).GetValue(...) works when starting reflection from the derived type.
  • Add a new VendorTests test project to validate EFCore.BulkExtensions compatibility against SQLite in-memory.
  • Add central package version for EFCore.BulkExtensions and include the new test project in the solution.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs Adds a shadow _queryContextFactory field to restore EFCore.BulkExtensions reflection compatibility.
tests/EntityFrameworkCore.Projectables.VendorTests/EntityFrameworkCore.Projectables.VendorTests.csproj New vendor test project targeting net8.0 with BulkExtensions + SQLite.
tests/EntityFrameworkCore.Projectables.VendorTests/TestContext.cs Test entity + SQLite in-memory DbContext configured with UseProjectables().
tests/EntityFrameworkCore.Projectables.VendorTests/EFCoreBulkExtensionsCompatibilityTests.cs Verifies BatchUtil.GetDbContext and batch operations don’t fail due to reflection (TargetException).
EntityFrameworkCore.Projectables.sln Adds the new VendorTests project to the solution.
Directory.Packages.props Adds central version entry for EFCore.BulkExtensions 8.0.4.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@PhenX PhenX marked this pull request as ready for review April 11, 2026 09:55
@PhenX PhenX merged commit fabb896 into master Apr 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

only update EntityFrameworkCore.Projectables 2.3.0 to 3.0.X Causes EFCore.BulkExtensions bulk deletions and updates to fail

3 participants