Skip to content

Add AsExpandedProperties IQueryable extension method#199

Draft
Copilot wants to merge 2 commits intomasterfrom
copilot/research-projectable-attributes-architecture
Draft

Add AsExpandedProperties IQueryable extension method#199
Copilot wants to merge 2 commits intomasterfrom
copilot/research-projectable-attributes-architecture

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 11, 2026

Summary

Adds AsExpandedProperties<TModel>() to QueryableExtensions — an IQueryable<T> extension that auto-injects a SELECT projection containing all EF-mapped columns plus the expression-expanded values of every writable [Projectable] property.

Problem

On a tracking DbContext the existing auto-inject logic is intentionally suppressed (to preserve change-tracking semantics):

// Tracking context — projectable properties are NOT populated:
var user = await context.Users.FirstAsync();
user.ComputedProp; // default(int) — not populated

The fix proposed in the architecture analysis: expose a method that explicitly opts into the auto-inject regardless of tracking mode.

Solution

// Now works on tracking contexts too:
var user = await context.Users.AsExpandedProperties().FirstAsync();
user.ComputedProp; // correctly populated via expression expansion

AsExpandedProperties() delegates to ExpandProjectables(), which uses a fresh ProjectableExpressionReplacer with trackByDefault: false. This forces the select injection before the expression tree is handed to EF Core's query compiler, so the compiler's own tracking guard never triggers.

Read-only projectable properties (those without a setter) are excluded from the injected projection because EF Core's materialiser cannot set them; only writable projectables appear in the generated SELECT.

Recommended usage: call AsExpandedProperties() after Where/OrderBy and before terminal operators (FirstAsync, ToListAsync, etc.):

await context.Users
    .Where(u => u.IsActive)
    .AsExpandedProperties()
    .ToListAsync();

Changes

File Change
src/.../Extensions/QueryableExtensions.cs New AsExpandedProperties<TModel>() with XML documentation
tests/.../AsExpandedPropertiesTests.cs 3 functional tests (tracking context, tracking with WHERE, NoTracking context)
tests/.../*.verified.txt × 9 Snapshot files for all 3 tests × 3 TFMs

Snapshots

Tracking context (the new case):

SELECT [e].[Id], [e].[Id] * 3
FROM [Entity] AS [e]

Tracking context with WHERE:

SELECT [e].[Id], [e].[Id] * 3
FROM [Entity] AS [e]
WHERE [e].[Id] > 0

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

Adds an opt-in IQueryable<T> extension (AsExpandedProperties<TModel>()) to force projectable-property select injection even on tracking queries, along with functional test coverage and snapshots to validate the generated SQL.

Changes:

  • Add AsExpandedProperties<TModel>() to QueryableExtensions as a user-facing API for explicit expanded-property projection.
  • Add functional tests covering tracking query root, tracking + Where, and no-tracking behavior.
  • Add Verify snapshots for the new functional tests across net8.0, net9.0, and net10.0.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/EntityFrameworkCore.Projectables/Extensions/QueryableExtensions.cs Adds AsExpandedProperties<TModel>() with XML docs; currently delegates to ExpandProjectables().
tests/EntityFrameworkCore.Projectables.FunctionalTests/AsExpandedPropertiesTests.cs New functional tests validating SQL projection injection behavior for tracking and no-tracking queries.
tests/EntityFrameworkCore.Projectables.FunctionalTests/AsExpandedPropertiesTests.TrackingContext_QueryRoot_InjectsWritableProjectableProperties.DotNet8_0.verified.txt Snapshot for tracking query-root case (net8).
tests/EntityFrameworkCore.Projectables.FunctionalTests/AsExpandedPropertiesTests.TrackingContext_QueryRoot_InjectsWritableProjectableProperties.DotNet9_0.verified.txt Snapshot for tracking query-root case (net9).
tests/EntityFrameworkCore.Projectables.FunctionalTests/AsExpandedPropertiesTests.TrackingContext_QueryRoot_InjectsWritableProjectableProperties.DotNet10_0.verified.txt Snapshot for tracking query-root case (net10).
tests/EntityFrameworkCore.Projectables.FunctionalTests/AsExpandedPropertiesTests.TrackingContext_WithWhere_InjectsProjectablePropertiesAfterFilter.DotNet8_0.verified.txt Snapshot for tracking + Where case (net8).
tests/EntityFrameworkCore.Projectables.FunctionalTests/AsExpandedPropertiesTests.TrackingContext_WithWhere_InjectsProjectablePropertiesAfterFilter.DotNet9_0.verified.txt Snapshot for tracking + Where case (net9).
tests/EntityFrameworkCore.Projectables.FunctionalTests/AsExpandedPropertiesTests.TrackingContext_WithWhere_InjectsProjectablePropertiesAfterFilter.DotNet10_0.verified.txt Snapshot for tracking + Where case (net10).
tests/EntityFrameworkCore.Projectables.FunctionalTests/AsExpandedPropertiesTests.NoTrackingContext_QueryRoot_InjectsProjectableProperties.DotNet8_0.verified.txt Snapshot for no-tracking query-root case (net8).
tests/EntityFrameworkCore.Projectables.FunctionalTests/AsExpandedPropertiesTests.NoTrackingContext_QueryRoot_InjectsProjectableProperties.DotNet9_0.verified.txt Snapshot for no-tracking query-root case (net9).
tests/EntityFrameworkCore.Projectables.FunctionalTests/AsExpandedPropertiesTests.NoTrackingContext_QueryRoot_InjectsProjectableProperties.DotNet10_0.verified.txt Snapshot for no-tracking query-root case (net10).

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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.

3 participants