Skip to content

Remove all allocations when resolving and make it even faster#189

Merged
PhenX merged 3 commits intomasterfrom
optimize-resolver-again
Mar 21, 2026
Merged

Remove all allocations when resolving and make it even faster#189
PhenX merged 3 commits intomasterfrom
optimize-resolver-again

Conversation

@PhenX
Copy link
Member

@PhenX PhenX commented Mar 20, 2026

(Also add a benchmark for constructor resolution)

It is now as fast as the resgistry, so ... is the registry still needed ?

image

Copy link
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

This PR optimizes the reflection-based fallback in ProjectionExpressionResolver by caching fully-resolved LambdaExpression instances (instead of rebuilding them per call), and extends the benchmarks to include constructor resolution so registry vs. reflection performance can be compared more directly.

Changes:

  • Replace the reflection “factory delegate” cache with a LambdaExpression cache to eliminate repeated expression-tree construction.
  • Switch the reflection path to invoke the generated Expression method once and reuse the resulting expression instance.
  • Add a projectable copy-constructor and benchmark coverage for constructor resolution (registry and reflection paths).

Reviewed changes

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

File Description
src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs Cache resolved reflection expressions (and a null sentinel) instead of caching compiled factories.
benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntity.cs Add a projectable copy constructor (and explicit parameterless ctor) for benchmarking constructor resolution.
benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ExpressionResolverBenchmark.cs Add registry/reflection benchmark cases for resolving the copy constructor expression.
Comments suppressed due to low confidence (1)

src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs:311

  • expressionFactoryType.MakeGenericType(originalDeclaringType.GenericTypeArguments) will throw when originalDeclaringType is a generic type definition (because GenericTypeArguments is empty). Since FindGeneratedExpressionViaReflection is public and documented for open-generic scenarios, consider handling originalDeclaringType.IsGenericTypeDefinition explicitly (e.g., use GetGenericArguments() or short-circuit with a clear return/exception) before calling MakeGenericType.
            if (expressionFactoryType.IsGenericTypeDefinition)
            {
                expressionFactoryType = expressionFactoryType.MakeGenericType(originalDeclaringType.GenericTypeArguments);
            }

PhenX and others added 2 commits March 21, 2026 00:02
…sionResolver.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…sionResolver.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@koenbeuk
Copy link
Collaborator

It is now as fast as the resgistry, so ... is the registry still needed ?

Likely yes, as the registry acts as a cold cache for many companion expressions and BuildReflectionExpression may still run many times concurrently upon startup if multiple concurrent requests hit it

@PhenX PhenX merged commit a97ee28 into master Mar 21, 2026
4 checks passed
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