From 81bd171a6311f8e0d526f7f00e73577e85602300 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 7 Oct 2025 09:10:10 +0200 Subject: [PATCH] Ensure case-insensitive parameter name uniqueness Fixes #36917 --- .../Internal/RelationalParameterProcessor.cs | 2 +- .../Query/NorthwindWhereQueryCosmosTest.cs | 38 ++++++++++++++++--- .../Query/NorthwindWhereQueryTestBase.cs | 19 ++++++++-- .../Query/NorthwindWhereQuerySqlServerTest.cs | 22 +++++++++-- 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs b/src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs index 392a94f28a9..433b0b317c1 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs @@ -31,7 +31,7 @@ private readonly IDictionary _visitedFromSqlExpre /// (i.e. they're prefixed), since /// can be prefixed or not. /// - private readonly HashSet _prefixedParameterNames = []; + private readonly HashSet _prefixedParameterNames = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _sqlParameters = new(); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs index 8d066cc59e5..f91ad1dee97 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs @@ -1529,13 +1529,39 @@ public override async Task Using_same_parameter_twice_in_query_generates_one_sql AssertSql(); } - public override async Task Two_parameters_with_same_name_get_uniquified(bool async) - { - // Concat with conversion, issue #34963. - await AssertTranslationFailed(() => base.Using_same_parameter_twice_in_query_generates_one_sql_parameter(async)); + public override Task Two_parameters_with_same_name_get_uniquified(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Two_parameters_with_same_name_get_uniquified(async); - AssertSql(); - } + AssertSql( + """ +@customerId='ANATR' +@customerId0='ALFKI' + +SELECT VALUE c +FROM root c +WHERE ((c["id"] = @customerId) OR (c["id"] = @customerId0)) +"""); + }); + + public override Task Two_parameters_with_same_case_insensitive_name_get_uniquified(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Two_parameters_with_same_case_insensitive_name_get_uniquified(async); + + AssertSql( + """ +@customerID='ANATR' +@customerId='ALFKI' + +SELECT VALUE c +FROM root c +WHERE ((c["id"] = @customerID) OR (c["id"] = @customerId)) +"""); + }); public override async Task Where_Queryable_ToList_Count(bool async) { diff --git a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs index 69c990d6845..e4beae2c27e 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs @@ -1327,16 +1327,27 @@ public virtual Task Using_same_parameter_twice_in_query_generates_one_sql_parame .Select(c => c.CustomerID)); } + private readonly string customerId = "ALFKI"; + [ConditionalTheory, MemberData(nameof(IsAsyncData))] public virtual Task Two_parameters_with_same_name_get_uniquified(bool async) { - var i = 10; + var customerId = "ANATR"; + + return AssertQuery( + async, + ss => ss.Set().Where(c => c.CustomerID == customerId || c.CustomerID == this.customerId)); + } + + [ConditionalTheory, MemberData(nameof(IsAsyncData))] + public virtual Task Two_parameters_with_same_case_insensitive_name_get_uniquified(bool async) + { + var customerID = "ANATR"; - // i+1 and i+2 each get parameterized using the same parameter name (since they're complex expressions). - // This exercises that query parameters are properly uniquified. + // Note the parameter names differ only by case (customerID vs. customerId) return AssertQuery( async, - ss => ss.Set().Where(c => (c.CustomerID + (i + 1)) + (c.CustomerID + (i + 2)) == "ALFKI11ALFKI12")); + ss => ss.Set().Where(c => c.CustomerID == customerID || c.CustomerID == customerId)); } [ConditionalTheory, MemberData(nameof(IsAsyncData))] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index bd0137646c4..63a234e7e7c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -1704,19 +1704,33 @@ WHERE CAST(@i AS nvarchar(max)) + [c].[CustomerID] + CAST(@i AS nvarchar(max)) = """); } - [ConditionalTheory] public override async Task Two_parameters_with_same_name_get_uniquified(bool async) { await base.Two_parameters_with_same_name_get_uniquified(async); AssertSql( """ -@p='11' -@p0='12' +@customerId='ANATR' (Size = 5) (DbType = StringFixedLength) +@customerId0='ALFKI' (Size = 5) (DbType = StringFixedLength) + +SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] = @customerId OR [c].[CustomerID] = @customerId0 +"""); + } + + public override async Task Two_parameters_with_same_case_insensitive_name_get_uniquified(bool async) + { + await base.Two_parameters_with_same_case_insensitive_name_get_uniquified(async); + +AssertSql( +""" +@customerID='ANATR' (Size = 5) (DbType = StringFixedLength) +@customerId0='ALFKI' (Size = 5) (DbType = StringFixedLength) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[CustomerID] + CAST(@p AS nvarchar(max)) + [c].[CustomerID] + CAST(@p0 AS nvarchar(max)) = N'ALFKI11ALFKI12' +WHERE [c].[CustomerID] = @customerID OR [c].[CustomerID] = @customerId0 """); }