From 4bd878e9c921b79bb8f1490814edcce6bca35d72 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 22 Apr 2026 16:36:16 +0200 Subject: [PATCH] Better error when using EF.Constant/Parameter with compiled queries or query filters When EF.Constant(), EF.Parameter(), or EF.MultipleParameters() are used inside compiled queries or query filters, throw a clear InvalidOperationException instead of a cryptic InvalidCastException. The check is in ExpressionTreeFuncletizer where parameterize is known to be false for these contexts. Closes #38151 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/EFCore/EFCore.baseline.json | 3 ++ src/EFCore/Properties/CoreStrings.Designer.cs | 8 ++++ src/EFCore/Properties/CoreStrings.resx | 3 ++ .../Internal/ExpressionTreeFuncletizer.cs | 10 ++++ .../Query/AdHocQueryFiltersQueryTestBase.cs | 48 +++++++++++++++++++ .../Query/NorthwindCompiledQueryTestBase.cs | 26 ++++++++++ .../NorthwindCompiledQuerySqlServerTest.cs | 6 +++ 7 files changed, 104 insertions(+) diff --git a/src/EFCore/EFCore.baseline.json b/src/EFCore/EFCore.baseline.json index 5741dadeed3..24e1fc07293 100644 --- a/src/EFCore/EFCore.baseline.json +++ b/src/EFCore/EFCore.baseline.json @@ -3970,6 +3970,9 @@ { "Member": "static string DuplicateTrigger(object? trigger, object? entityType, object? conflictingEntityType);" }, + { + "Member": "static string EFMethodNotSupportedInCompiledQueries(object? methodName);" + }, { "Member": "static string EFMethodWithNonEvaluatableArgument(object? methodName);" }, diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index cc2c2446906..918ccec6adc 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -1228,6 +1228,14 @@ public static string EFConstantNotSupported public static string EFConstantNotSupportedInPrecompiledQueries => GetString("EFConstantNotSupportedInPrecompiledQueries"); + /// + /// '{methodName}' is not supported when using compiled queries or query filters. + /// + public static string EFMethodNotSupportedInCompiledQueries(object? methodName) + => string.Format( + GetString("EFMethodNotSupportedInCompiledQueries", nameof(methodName)), + methodName); + /// /// The '{methodName}' method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 903879229d3..1b2a3fc1494 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -576,6 +576,9 @@ The 'EF.Constant<T>' method is not supported when using precompiled queries. + + '{methodName}' is not supported when using compiled queries or query filters. + The '{methodName}' method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities. diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs index f1722590db0..1f06f2aaf84 100644 --- a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs +++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs @@ -934,6 +934,11 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall) throw new InvalidOperationException(CoreStrings.EFConstantNotSupportedInPrecompiledQueries); } + if (!_parameterize) + { + throw new InvalidOperationException(CoreStrings.EFMethodNotSupportedInCompiledQueries("EF.Constant")); + } + var argument = Visit(methodCall.Arguments[0], out var argumentState); if (!argumentState.IsEvaluatable) @@ -1118,6 +1123,11 @@ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] Expression HandleParameter(MethodCallExpression methodCall, string methodName) { + if (!_parameterize) + { + throw new InvalidOperationException(CoreStrings.EFMethodNotSupportedInCompiledQueries(methodName)); + } + var argument = Visit(methodCall.Arguments[0], out var argumentState); if (!argumentState.IsEvaluatable) diff --git a/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs index 7d0f0f352e0..a07693f2981 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs @@ -850,4 +850,52 @@ public class Entity38132 } #endregion + + #region 38151 + + [ConditionalFact] + public virtual async Task Query_filter_with_EF_Constant_throws() + { + var contextFactory = await InitializeNonSharedTest(); + using var context = contextFactory.CreateDbContext(); + + var message = Assert.Throws(() => context.Set().ToList()).Message; + Assert.Equal(CoreStrings.EFMethodNotSupportedInCompiledQueries("EF.Constant"), message); + } + + protected class Context38151_Constant(DbContextOptions options) : DbContext(options) + { + public int TenantId { get; set; } = 1; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + => modelBuilder.Entity() + .HasQueryFilter(e => e.TenantId == EF.Constant(TenantId)); + } + + [ConditionalFact] + public virtual async Task Query_filter_with_EF_Parameter_throws() + { + var contextFactory = await InitializeNonSharedTest(); + using var context = contextFactory.CreateDbContext(); + + var message = Assert.Throws(() => context.Set().ToList()).Message; + Assert.Equal(CoreStrings.EFMethodNotSupportedInCompiledQueries("EF.Parameter"), message); + } + + protected class Context38151_Parameter(DbContextOptions options) : DbContext(options) + { + public int TenantId { get; set; } = 1; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + => modelBuilder.Entity() + .HasQueryFilter(e => e.TenantId == EF.Parameter(TenantId)); + } + + public class Entity38151 + { + public int Id { get; set; } + public int TenantId { get; set; } + } + + #endregion } diff --git a/test/EFCore.Specification.Tests/Query/NorthwindCompiledQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindCompiledQueryTestBase.cs index 73346af6d4a..4b5fa4d554e 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindCompiledQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindCompiledQueryTestBase.cs @@ -801,6 +801,32 @@ await asyncSingleResultQueryWithCancellationToken( "CHOPS", "CONSH", default)); } + [ConditionalFact] + public virtual void Compiled_query_with_EF_Constant_throws() + { + var query = EF.CompileQuery( + (NorthwindContext context) => context.Customers.Where(c => c.CustomerID == EF.Constant("ALFKI"))); + + using var context = CreateContext(); + + var message = Assert.Throws(() => query(context).ToList()).Message; + Assert.Equal(CoreStrings.EFMethodNotSupportedInCompiledQueries("EF.Constant"), message); + } + + [ConditionalFact] + public virtual void Compiled_query_with_EF_Parameter_throws() + { + var customerID = "ALFKI"; + + var query = EF.CompileQuery( + (NorthwindContext context) => context.Customers.Where(c => c.CustomerID == EF.Parameter(customerID))); + + using var context = CreateContext(); + + var message = Assert.Throws(() => query(context).ToList()).Message; + Assert.Equal(CoreStrings.EFMethodNotSupportedInCompiledQueries("EF.Parameter"), message); + } + protected async Task CountAsync(IAsyncEnumerable source) { var count = 0; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindCompiledQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindCompiledQuerySqlServerTest.cs index 2bf9c43004f..77a8da4d577 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindCompiledQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindCompiledQuerySqlServerTest.cs @@ -759,6 +759,12 @@ ORDER BY [c].[CustomerID] """); } + public override void Compiled_query_with_EF_Constant_throws() + => base.Compiled_query_with_EF_Constant_throws(); + + public override void Compiled_query_with_EF_Parameter_throws() + => base.Compiled_query_with_EF_Parameter_throws(); + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); }