diff --git a/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs b/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs index 0acb667f234..dc85947baf0 100644 --- a/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs @@ -54,7 +54,7 @@ public ParameterExtractingExpressionVisitor( bool generateContextAccessors) { _evaluatableExpressionFindingExpressionVisitor - = new EvaluatableExpressionFindingExpressionVisitor(evaluatableExpressionFilter, model); + = new EvaluatableExpressionFindingExpressionVisitor(evaluatableExpressionFilter, model, parameterize); _parameterValues = parameterValues; _logger = logger; @@ -467,16 +467,19 @@ private sealed class EvaluatableExpressionFindingExpressionVisitor : ExpressionV private readonly IEvaluatableExpressionFilter _evaluatableExpressionFilter; private readonly ISet _allowedParameters = new HashSet(); private readonly IModel _model; + private readonly bool _parameterize; private bool _evaluatable; private bool _containsClosure; private bool _inLambda; private IDictionary _evaluatableExpressions; - public EvaluatableExpressionFindingExpressionVisitor(IEvaluatableExpressionFilter evaluatableExpressionFilter, IModel model) + public EvaluatableExpressionFindingExpressionVisitor( + IEvaluatableExpressionFilter evaluatableExpressionFilter, IModel model, bool parameterize) { _evaluatableExpressionFilter = evaluatableExpressionFilter; _model = model; + _parameterize = parameterize; } public IDictionary Find(Expression expression) @@ -504,7 +507,9 @@ public override Expression Visit(Expression expression) _evaluatable = IsEvaluatableNodeType(expression) // Extension point to disable funcletization - && _evaluatableExpressionFilter.IsEvaluatableExpression(expression, _model); + && _evaluatableExpressionFilter.IsEvaluatableExpression(expression, _model) + // Don't evaluate QueryableMethods if in compiled query + && (_parameterize || !IsQueryableMethod(expression)); _containsClosure = false; base.Visit(expression); @@ -643,19 +648,13 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio } private static bool IsEvaluatableNodeType(Expression expression) - { - if (expression.NodeType == ExpressionType.Extension) - { - if (!expression.CanReduce) - { - return false; - } + => expression.NodeType != ExpressionType.Extension + || expression.CanReduce + && IsEvaluatableNodeType(expression.ReduceAndCheck()); - return IsEvaluatableNodeType(expression.ReduceAndCheck()); - } - - return true; - } + private static bool IsQueryableMethod(Expression expression) + => expression is MethodCallExpression methodCallExpression + && methodCallExpression.Method.DeclaringType == typeof(Queryable); } } } diff --git a/test/EFCore.Specification.Tests/Query/NorthwindCompiledQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindCompiledQueryTestBase.cs index d33b08e7380..c0346e423f8 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindCompiledQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindCompiledQueryTestBase.cs @@ -42,7 +42,7 @@ public virtual void DbSet_query() public virtual void DbSet_query_first() { var query = EF.CompileQuery( - (NorthwindContext context) => context.Customers.OrderBy(c => c.CustomerID).First()); + (NorthwindContext context) => context.Set().OrderBy(c => c.CustomerID).First()); using (var context = CreateContext()) { @@ -540,6 +540,23 @@ public virtual void Compiled_query_when_does_not_end_in_query_operator() } } + [ConditionalFact(Skip = "Issue#19209")] + public virtual void Compiled_query_when_using_member_on_context() + { + var query = EF.CompileQuery( + (NorthwindContext context) + => context.Customers.Where(c => c.CustomerID.StartsWith(context.TenantPrefix))); + + using (var context = CreateContext()) + { + context.TenantPrefix = "A"; + Assert.Equal(6, query(context).Count()); + + context.TenantPrefix = "B"; + Assert.Equal(4, query(context).Count()); + } + } + protected NorthwindContext CreateContext() => Fixture.CreateContext(); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 64885e89dfe..163cc198cdf 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -2214,6 +2214,29 @@ public virtual void Query_cache_entries_are_evicted_as_necessary() } } + [ConditionalFact] + public virtual void Explicitly_compiled_query_does_not_add_cache_entry() + { + var parameter = Expression.Parameter(typeof(Entity8909)); + var predicate = Expression.Lambda>( + Expression.MakeBinary(ExpressionType.Equal, + Expression.PropertyOrField(parameter, "Id"), + Expression.Constant(1)), + parameter); + var query = EF.CompileQuery((MyContext8909 context) => context.Set().SingleOrDefault(predicate)); + using (CreateDatabase8909()) + { + using var context = new MyContext8909(_options); + context.Cache.Compact(1); + Assert.Equal(0, context.Cache.Count); + + query(context); + + // 1 entry for RelationalCommandCache + Assert.Equal(1, context.Cache.Count); + } + } + private SqlServerTestStore CreateDatabase8909() { return CreateTestStore(