Conversation
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
…benchmark comparison Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
|
@copilot the generated code for the registry is too repetitive, find a way to specify the registry data in an efficient data structure and create an efficient method to fill the registry. |
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Done in 7e49dc7. The generated private static Dictionary<nint, LambdaExpression> Build()
{
const BindingFlags allFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
var map = new Dictionary<nint, LambdaExpression>();
Register(map, typeof(global::Foo.C).GetProperty("IdPlus1", allFlags)?.GetMethod, "...Foo_C_IdPlus1");
Register(map, typeof(global::Foo.C).GetMethod("Add", allFlags, null, new global::System.Type[] { typeof(int) }, null), "...Foo_C_Add_P0_int");
return map;
}
private static void Register(Dictionary<nint, LambdaExpression> map, MethodBase? m, string exprClass)
{
if (m is null) return;
var exprType = m.DeclaringType?.Assembly.GetType(exprClass);
var exprMethod = exprType?.GetMethod("Expression", BindingFlags.Static | BindingFlags.NonPublic);
if (exprMethod is not null)
map[m.MethodHandle.Value] = (LambdaExpression)exprMethod.Invoke(null, null)!;
}Also added |
There was a problem hiding this comment.
Pull request overview
This PR introduces a source-generated static projection registry to enable AOT-friendlier, low-overhead [Projectable] resolution by preferring an O(1) registry lookup over repeated reflection, and adds tests/benchmarks to validate and measure the impact.
Changes:
- Generator emits
ProjectionRegistry.g.csaggregating projectables into a static dictionary keyed byMethodHandle.Value, using SyntaxFactory-based emission. - Runtime resolver prefers the registry (cached per-assembly) and exposes an explicit reflection-only slow path for benchmarks/fallback.
- Tests/benchmarks updated and expanded to cover registry generation and measure cold-start + resolver overhead.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/EntityFrameworkCore.Projectables.Generator.Tests/RegistryTests.cs | Adds dedicated tests asserting registry emission and contents. |
| tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTestsBase.cs | Wraps generator run results to filter out ProjectionRegistry.g.cs for existing tests. |
| src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs | Adds registry-first lookup with per-assembly caching + reflection-only fallback API. |
| src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs | Makes _select / _where static readonly using expression-tree extraction (trim-safe). |
| src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs | Adds incremental pipeline to collect entries and emit ProjectionRegistry.g.cs. |
| src/EntityFrameworkCore.Projectables.Generator/ProjectableRegistryEntry.cs | Adds record used to represent registry entries in incremental pipeline. |
| src/EntityFrameworkCore.Projectables.Generator/IsExternalInit.cs | Adds IsExternalInit polyfill for record support on netstandard2.0 generator target. |
| benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ResolverOverhead.cs | Adds cold-start per-DbContext resolver overhead benchmark. |
| benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ExpressionResolverBenchmark.cs | Adds micro-benchmarks comparing registry vs reflection resolution. |
| benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableProperties.cs | Adds MemoryDiagnoser attribute. |
| benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableMethods.cs | Adds MemoryDiagnoser attribute. |
| benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ProjectableExtensionMethods.cs | Adds MemoryDiagnoser attribute. |
| benchmarks/EntityFrameworkCore.Projectables.Benchmarks/PlainOverhead.cs | Adds MemoryDiagnoser attribute. |
| benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntity.cs | Adds IdPlusDelta(int) projectable for parameter-type disambiguation benchmarking. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs
Outdated
Show resolved
Hide resolved
src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs
Outdated
Show resolved
Hide resolved
src/EntityFrameworkCore.Projectables.Generator/ProjectableRegistryEntry.cs
Show resolved
Hide resolved
...s/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTestsBase.cs
Show resolved
Hide resolved
|
@copilot implement review suggestions |
…bleArray, lambda fix, StringComparison.Ordinal Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Done in e68b8d6. All four review suggestions implemented:
|
…e-projection-registry # Conflicts: # src/EntityFrameworkCore.Projectables.Generator/ProjectionExpressionGenerator.cs # tests/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTestsBase.cs
…-projection-registry' into copilot/implement-aot-compatible-projection-registry
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tests/EntityFrameworkCore.Projectables.Generator.Tests/RegistryTests.cs
Outdated
Show resolved
Hide resolved
tests/EntityFrameworkCore.Projectables.Generator.Tests/RegistryTests.cs
Outdated
Show resolved
Hide resolved
...s/EntityFrameworkCore.Projectables.Generator.Tests/ProjectionExpressionGeneratorTestsBase.cs
Show resolved
Hide resolved
Move code generation to a string builder (much faster)
Updated [EntityFrameworkCore.Projectables](https://github.com/EFNext/EntityFrameworkCore.Projectables) from 5.0.2 to 6.0.0. <details> <summary>Release notes</summary> _Sourced from [EntityFrameworkCore.Projectables's releases](https://github.com/EFNext/EntityFrameworkCore.Projectables/releases)._ ## 6.0.0 ## What's Changed ### Major changes * Add support for projectable method overloads by @PhenX in EFNext/EntityFrameworkCore.Projectables#143 * Add C#14 extension members by @PhenX in EFNext/EntityFrameworkCore.Projectables#148 * Support explicitly implemented interface members and default interface properties by @rhodon-jargon in EFNext/EntityFrameworkCore.Projectables#135 * Support block-bodied members with [Projectable] attribute by @Copilot in EFNext/EntityFrameworkCore.Projectables#152 * Add ExpandEnumMethods to expand enum extension calls into ternary expressions by @Copilot in EFNext/EntityFrameworkCore.Projectables#150 * Add support for pattern matching in various cases by @PhenX in EFNext/EntityFrameworkCore.Projectables#158 * Add support for projectable constructors by @PhenX in EFNext/EntityFrameworkCore.Projectables#161 * AOT-compatible static projection registry + SyntaxFactory-based emission by @Copilot in EFNext/EntityFrameworkCore.Projectables#166 * Improve UseMemberBody and add new diagnostics by @PhenX in EFNext/EntityFrameworkCore.Projectables#174 * Add code fixes for EFP0001, EFP0002 and EFP0008, with tests by @PhenX in EFNext/EntityFrameworkCore.Projectables#181 * Add a code fixer and a code refactorer to transform factory methods into constructors by @PhenX in EFNext/EntityFrameworkCore.Projectables#183 * Improve source generator and analyzer responsiveness by @PhenX in EFNext/EntityFrameworkCore.Projectables#171 * Docs website by @PhenX in EFNext/EntityFrameworkCore.Projectables#194 * Implement global MSBuild defaults for [Projectable] options by @koenbeuk in EFNext/EntityFrameworkCore.Projectables#191 ### Other changes * Check source generator output compilation by @PhenX in EFNext/EntityFrameworkCore.Projectables#156 * Fix GetImplementingProperty issue when using interfaces by @PhenX in EFNext/EntityFrameworkCore.Projectables#144 * Full qualification not needed anymore by @PhenX in EFNext/EntityFrameworkCore.Projectables#157 * Split generator tests in different classes by @PhenX in EFNext/EntityFrameworkCore.Projectables#162 * Refactor ExpressionSyntaxRewriter into focused partial class files by @Copilot in EFNext/EntityFrameworkCore.Projectables#163 * Update readme and add new test for constructors by @PhenX in EFNext/EntityFrameworkCore.Projectables#165 * Remove obsolete code by @PhenX in EFNext/EntityFrameworkCore.Projectables#167 * Add source generator self-benchmark covering all expression transformers by @Copilot in EFNext/EntityFrameworkCore.Projectables#168 * Fix null conditional rewrite generating invalid member access on nullable value types by @Copilot in EFNext/EntityFrameworkCore.Projectables#169 * Improve generator benchmark with more realistic scenario by @PhenX in EFNext/EntityFrameworkCore.Projectables#170 * Fix stale incremental generator cache when referenced types change in other source files by @Copilot in EFNext/EntityFrameworkCore.Projectables#172 * Add tests for properties with a getter and setter by @PhenX in EFNext/EntityFrameworkCore.Projectables#173 * UseMemberBody more strict with expressions by @PhenX in EFNext/EntityFrameworkCore.Projectables#175 * Add custom Copilot instructions by @PhenX in EFNext/EntityFrameworkCore.Projectables#177 * Reorganize generator project by @PhenX in EFNext/EntityFrameworkCore.Projectables#178 * Add closure resolution benchmark by @PhenX in EFNext/EntityFrameworkCore.Projectables#179 * Feature/optimize closures by @PhenX in EFNext/EntityFrameworkCore.Projectables#180 * Feature/optimize resolver by @PhenX in EFNext/EntityFrameworkCore.Projectables#182 * Update github project urls by @PhenX in EFNext/EntityFrameworkCore.Projectables#184 * Update deps by @PhenX in EFNext/EntityFrameworkCore.Projectables#185 * Remove obsolete verified files and name net8 files correctly by @PhenX in EFNext/EntityFrameworkCore.Projectables#186 * Remove all allocations when resolving and make it even faster by @PhenX in EFNext/EntityFrameworkCore.Projectables#189 * Add devcontainer configuration for C# (.NET) development by @koenbeuk in EFNext/EntityFrameworkCore.Projectables#190 **Full Changelog**: EFNext/EntityFrameworkCore.Projectables@v5.0.2...v6.0.0 Commits viewable in [compare view](EFNext/EntityFrameworkCore.Projectables@v5.0.2...v6.0.0). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: James Gunn <james@gunn.io>
Updated [EntityFrameworkCore.Projectables](https://github.com/EFNext/EntityFrameworkCore.Projectables) from 5.0.2 to 6.0.0. <details> <summary>Release notes</summary> _Sourced from [EntityFrameworkCore.Projectables's releases](https://github.com/EFNext/EntityFrameworkCore.Projectables/releases)._ ## 6.0.0 ## What's Changed ### Major changes * Add support for projectable method overloads by @PhenX in EFNext/EntityFrameworkCore.Projectables#143 * Add C#14 extension members by @PhenX in EFNext/EntityFrameworkCore.Projectables#148 * Support explicitly implemented interface members and default interface properties by @rhodon-jargon in EFNext/EntityFrameworkCore.Projectables#135 * Support block-bodied members with [Projectable] attribute by @Copilot in EFNext/EntityFrameworkCore.Projectables#152 * Add ExpandEnumMethods to expand enum extension calls into ternary expressions by @Copilot in EFNext/EntityFrameworkCore.Projectables#150 * Add support for pattern matching in various cases by @PhenX in EFNext/EntityFrameworkCore.Projectables#158 * Add support for projectable constructors by @PhenX in EFNext/EntityFrameworkCore.Projectables#161 * AOT-compatible static projection registry + SyntaxFactory-based emission by @Copilot in EFNext/EntityFrameworkCore.Projectables#166 * Improve UseMemberBody and add new diagnostics by @PhenX in EFNext/EntityFrameworkCore.Projectables#174 * Add code fixes for EFP0001, EFP0002 and EFP0008, with tests by @PhenX in EFNext/EntityFrameworkCore.Projectables#181 * Add a code fixer and a code refactorer to transform factory methods into constructors by @PhenX in EFNext/EntityFrameworkCore.Projectables#183 * Improve source generator and analyzer responsiveness by @PhenX in EFNext/EntityFrameworkCore.Projectables#171 * Docs website by @PhenX in EFNext/EntityFrameworkCore.Projectables#194 * Implement global MSBuild defaults for [Projectable] options by @koenbeuk in EFNext/EntityFrameworkCore.Projectables#191 ### Other changes * Check source generator output compilation by @PhenX in EFNext/EntityFrameworkCore.Projectables#156 * Fix GetImplementingProperty issue when using interfaces by @PhenX in EFNext/EntityFrameworkCore.Projectables#144 * Full qualification not needed anymore by @PhenX in EFNext/EntityFrameworkCore.Projectables#157 * Split generator tests in different classes by @PhenX in EFNext/EntityFrameworkCore.Projectables#162 * Refactor ExpressionSyntaxRewriter into focused partial class files by @Copilot in EFNext/EntityFrameworkCore.Projectables#163 * Update readme and add new test for constructors by @PhenX in EFNext/EntityFrameworkCore.Projectables#165 * Remove obsolete code by @PhenX in EFNext/EntityFrameworkCore.Projectables#167 * Add source generator self-benchmark covering all expression transformers by @Copilot in EFNext/EntityFrameworkCore.Projectables#168 * Fix null conditional rewrite generating invalid member access on nullable value types by @Copilot in EFNext/EntityFrameworkCore.Projectables#169 * Improve generator benchmark with more realistic scenario by @PhenX in EFNext/EntityFrameworkCore.Projectables#170 * Fix stale incremental generator cache when referenced types change in other source files by @Copilot in EFNext/EntityFrameworkCore.Projectables#172 * Add tests for properties with a getter and setter by @PhenX in EFNext/EntityFrameworkCore.Projectables#173 * UseMemberBody more strict with expressions by @PhenX in EFNext/EntityFrameworkCore.Projectables#175 * Add custom Copilot instructions by @PhenX in EFNext/EntityFrameworkCore.Projectables#177 * Reorganize generator project by @PhenX in EFNext/EntityFrameworkCore.Projectables#178 * Add closure resolution benchmark by @PhenX in EFNext/EntityFrameworkCore.Projectables#179 * Feature/optimize closures by @PhenX in EFNext/EntityFrameworkCore.Projectables#180 * Feature/optimize resolver by @PhenX in EFNext/EntityFrameworkCore.Projectables#182 * Update github project urls by @PhenX in EFNext/EntityFrameworkCore.Projectables#184 * Update deps by @PhenX in EFNext/EntityFrameworkCore.Projectables#185 * Remove obsolete verified files and name net8 files correctly by @PhenX in EFNext/EntityFrameworkCore.Projectables#186 * Remove all allocations when resolving and make it even faster by @PhenX in EFNext/EntityFrameworkCore.Projectables#189 * Add devcontainer configuration for C# (.NET) development by @koenbeuk in EFNext/EntityFrameworkCore.Projectables#190 **Full Changelog**: EFNext/EntityFrameworkCore.Projectables@v5.0.2...v6.0.0 Commits viewable in [compare view](EFNext/EntityFrameworkCore.Projectables@v5.0.2...v6.0.0). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: James Gunn <james@gunn.io>

EmitRegistryRegisterhelper +const BindingFlags allFlagsRegistryTests.cswith 8 testsConcurrentDictionarynull storage: sentinel delegate_nullRegistry = _ => null!avoids storing null values; callers useReferenceEqualsto detect "no registry"BuildTypeArrayExprlambda syntax:name => (ExpressionSyntax)TypeOfExpression(...)(typed-lambda syntax not valid in netstandard2.0 target)ImmutableArraystructural equality: newEquatableImmutableArraystruct with element-wiseSequenceEqualequality and polynomialGetHashCode(noSystem.HashCode— not available in netstandard2.0); implicit conversion to/fromImmutableArray<string>keeps all callers unchangedEndsWithstring comparison: both calls now useStringComparison.OrdinalBenchmarks
Source generator
Before :

After:

No impact on incremental, very small on non-incremental
Expression resolver
Registry based resolving is nearly twice as fast, and allocates nearly 10 times less memory 🎉