-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Description
EF Core 10's query translation generates invalid SQL when a LINQ query uses two different C# variables whose names differ only by casing (e.g., a method parameter named id and a local variable named ID). This causes a parameter name collision on case-insensitive databases like SQL Server.
The issue is not specific to any data type (Guid, int, etc.) or value source (constants, properties, variables). It is a general parameter naming bug where C# variable names are translated too directly into SQL parameter names without ensuring their uniqueness in a case-insensitive context.
This is a regression from EF Core 9, where parameter names were correctly sanitized to prevent these collisions. The change in behavior appears to be related to the parameter naming improvements introduced by PR #35200.
Steps to Reproduce
The following code reliably reproduces the issue. It uses a method parameter named guid (Guid) and references typeof(Order).GUID in the local expression.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;
public static async Task Main()
{
using (var context = new SampleContext())
{
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
var orders = References(context.Orders, Guid.New());
var query = (from order in orders
where order.SomeGuid == typeof(Order).GUID
select order);
Console.WriteLine(query.ToQueryString());
// This will throw the exception
await query.ToListAsync();
}
}
// Helper method to simulate a query filter which gets its guid from a method variable named "guid"
public static IQueryable<Order> References(IQueryable<Order> query, Guid guid)
{
return query.Where(q => q.SomeGuid == guid);
}
public class Order
{
public int Id { get; set; }
public Guid SomeGuid { get; set; }
}
public class SampleContext : DbContext
{
public DbSet<Order> Orders { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Integrated Security=true;Initial Catalog=EFCoreGuidBugTest")
.LogTo(Console.WriteLine, LogLevel.Information);
}
}Actual Behavior (EF Core 10)
EF Core 10 generates SQL that declares two variables whose names differ only in casing, causing a failure.
Generated SQL (EF Core 10 - Incorrect):
DECLARE @guid uniqueIdentifier = 'f3aaf539-13b4-343f-a029-271153b3c1c5';
DECLARE @GUID uniqueIdentifier = 'f3aaf539-13b4-343f-a029-271153b3c1c5';
SELECT [o].[Id], [o].[SomeGuid]
FROM [Orders] AS [o]
WHERE [o].[SomeGuid] = @guid AND [o].[SomeGuid] = @GUIDResulting Exception:
Microsoft.Data.SqlClient.SqlException (0x80131904): The variable name '@Guid' has already been declared. Variable names must be unique within a query batch or stored procedure.
Expected Behavior (Matches EF Core 9) 👍
The expected behavior is for EF Core to generate valid SQL with unique parameter names, as it did in EF Core 9.
DECLARE @__guid_0 uniqueIdentifier = 'e8f5a6c9-1172-3d0a-81a1-01341665983e';
DECLARE @__GUID_1 uniqueIdentifier = 'e8f5a6c9-1172-3d0a-81a1-01341665983e';
SELECT [o].[Id], [o].[SomeGuid]
FROM [Orders] AS [o]
WHERE [o].[SomeGuid] = @__guid_0 AND [o].[SomeGuid] = @__GUID_1Additional Notes
I want to add that I appreciate the improvements for more readable parameter names made in PR #35200. The goal of this issue is not to request a revert to the old naming scheme (e.g., @__guid_0).
The ideal solution would be for the new naming strategy to also handle potential case-insensitivity collisions, for example by adding a numeric suffix (@Guid, @Guid1) when a conflict is detected. This would preserve the enhanced readability while ensuring the generated SQL is always valid.