diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs index 45e6f36c87b..39759f5b6ec 100644 --- a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs +++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs @@ -31,6 +31,9 @@ public class ExpressionTreeFuncletizer : ExpressionVisitor private static readonly bool UseOldBehavior37974 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37974", out var enabled37974) && enabled37974; + private static readonly bool UseOldBehavior38132 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue38132", out var enabled38132) && enabled38132; + // The general algorithm here is the following. // 1. First, for each node type, visit that node's children and get their states (evaluatable, contains evaluatable, no evaluatable). // 2. Calculate the parent node's aggregate state from its children; a container node whose children are all evaluatable is itself @@ -2129,6 +2132,19 @@ bool PreserveConvertNode(Expression expression) } } + if (!UseOldBehavior38132) + { + // As a safety guard, if there's any problematic character in the name for any reason, fall back to "p". + foreach (var c in parameterName) + { + if (!char.IsLetterOrDigit(c) && c != '_') + { + parameterName = "p"; + break; + } + } + } + if (UseOldBehavior37152) { // Uniquify the parameter name @@ -2169,7 +2185,9 @@ bool PreserveConvertNode(Expression expression) if (visited != expression) { parameterName = QueryFilterPrefix - + (RemoveConvert(expression) is MemberExpression { Member.Name: var memberName } ? ("__" + memberName) : "__p"); + + (RemoveConvert(expression) is MemberExpression { Member.Name: var memberName } + ? "__" + (UseOldBehavior38132 ? memberName : SanitizeCompilerGeneratedName(memberName)) + : "__p"); isContextAccessor = true; // Context accessors (query filters accessing the context) never get constantized diff --git a/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs index c84d80fef18..a82b0b5d86b 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs @@ -815,4 +815,39 @@ public class FooBar35111 } #endregion + + #region 38132 + + [ConditionalFact] + public virtual async Task Query_filter_with_primary_constructor_parameter() + { + var contextFactory = await InitializeAsync( + addServices: s => + { + s.AddSingleton(typeof(Guid), + new Guid("00000001-0000-0000-0000-000000000001")); + return s; + }, + usePooling: false); + using var context = contextFactory.CreateContext(); + + var result = context.Set().ToList(); + Assert.Empty(result); + } + + protected class Context38132(DbContextOptions options, Guid tenantId) : DbContext(options) + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + => modelBuilder.Entity() + .HasQueryFilter(e => e.TenantId == tenantId); + } + + public class Entity38132 + { + public int Id { get; set; } + public string Name { get; set; } + public Guid TenantId { get; set; } + } + + #endregion } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQueryFiltersQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQueryFiltersQuerySqlServerTest.cs index d2e44796c43..3cb094d0ede 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQueryFiltersQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQueryFiltersQuerySqlServerTest.cs @@ -443,6 +443,20 @@ FROM [Locations] AS [l] ) AS [l0] ON [s].[LocationId] = [l0].[LocationId] WHERE [s].[IsDeleted] = 0 ORDER BY [s].[Name] +"""); + } + + public override async Task Query_filter_with_primary_constructor_parameter() + { + await base.Query_filter_with_primary_constructor_parameter(); + + AssertSql( + """ +@ef_filter__tenantId='00000001-0000-0000-0000-000000000001' + +SELECT [e].[Id], [e].[Name], [e].[TenantId] +FROM [Entity38132] AS [e] +WHERE [e].[TenantId] = @ef_filter__tenantId """); } }