From b50581ad639f6602f0c537c568b0fe8eaae94812 Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Tue, 26 Nov 2024 16:20:06 -0800 Subject: [PATCH] Fix to #35208 (#35211) Port of #35209 **Description** In EF9 we changed the way we generate shapers in preparation for AOT scenarios. We no longer can embed arbitrary objects into the shaper, instead we need to provide a way to construct that object in code (using LiftableConstant mechanism), or simulate the functionality it used to provide. At the end of our processing, we find all liftable constants and for the non-AOT case we compile their resolver lambdas and invoke the result with liftable context object to produce the resulting constant object we initially wanted. (in AOT case we generate code from the resolver lambda). Problem is that we are compiling the resolver lambda in the interpretation mode - if the final product is itself a delegate, that delegate will itself be in the interpreter mode and therefore less efficient when invoked multiple times when the query runs. Fix is to use regular compilation rather than interpretation. **Customer impact** Queries using collection navigation with significant amount of data suffer large performance degradation when compared with EF8. No good workaround. **How found** Multiple customer reports on 9.0.0 **Regression** Yes, from 8.0. **Testing** Ad-hoc perf testing with BenchmarkDotNet. Functional change already covered by numerous tests. **Risk** Low, quirk added. --- .../Query/RelationalLiftableConstantProcessor.cs | 2 +- src/EFCore/Query/LiftableConstantProcessor.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs b/src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs index 2d0b9fa8c0c..32445d46a71 100644 --- a/src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs +++ b/src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs @@ -36,7 +36,7 @@ protected override ConstantExpression InlineConstant(LiftableConstantExpression if (liftableConstant.ResolverExpression is Expression> resolverExpression) { - var resolver = resolverExpression.Compile(preferInterpretation: true); + var resolver = resolverExpression.Compile(preferInterpretation: UseOldBehavior35208); var value = resolver(_relationalMaterializerLiftableConstantContext); return Expression.Constant(value, liftableConstant.Type); } diff --git a/src/EFCore/Query/LiftableConstantProcessor.cs b/src/EFCore/Query/LiftableConstantProcessor.cs index 5e384638cd8..973601369bc 100644 --- a/src/EFCore/Query/LiftableConstantProcessor.cs +++ b/src/EFCore/Query/LiftableConstantProcessor.cs @@ -31,6 +31,9 @@ private sealed record LiftedConstant( private readonly LiftedConstantOptimizer _liftedConstantOptimizer = new(); private ParameterExpression? _contextParameter; + protected static readonly bool UseOldBehavior35208 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35208", out var enabled35208) && enabled35208; + /// /// Exposes all constants that have been lifted during the last invocation of . /// @@ -198,7 +201,7 @@ protected virtual ConstantExpression InlineConstant(LiftableConstantExpression l // Make sure there aren't any problematic un-lifted constants within the resolver expression. _unsupportedConstantChecker.Check(resolverExpression); - var resolver = resolverExpression.Compile(preferInterpretation: true); + var resolver = resolverExpression.Compile(preferInterpretation: UseOldBehavior35208); var value = resolver(_materializerLiftableConstantContext); return Expression.Constant(value, liftableConstant.Type);