Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The data types varchar and nvarchar are incompatible in the '^' operator #35093

Open
miqueljuan opened this issue Nov 13, 2024 · 4 comments
Open

Comments

@miqueljuan
Copy link

miqueljuan commented Nov 13, 2024

Projection to Boolean of Equals comparator of a Enum property (that has custom ValueConverter to string) with one of possibles values of Enum translates to SQL with the new XOR ('^') operator implementation, causing 'The data types varchar and nvarchar are incompatible in the '^' operator'.

dbContext.Orders.Select(order=> new OrderModel {  Paid = order.State == OrderState.Paid }).ToList();

enum OrderState { Unpaid, Paid, ... }

class OrderStateConverter : ValueConverter<OrderState, string> 
{
  private static string ConvertToString(OrderState state)
  {
      return stateswitch
      {
          OrderState.Unpaid=> "U",
          OrderState.Paid => "P",
          ....
          _ => throw new InvalidEnumArgumentException(nameof(state), (int)state, typeof(OrderState))
      };
  }
  private static OrderState ConvertToEnum(string state)
  {
       return state.ToUpperInvariant() switch
       {
           "U" => OrderState.Unpaid,
           "P" => OrderState.Paid,
            ....
           _ => throw new InvalidEnumArgumentException(...)
       };
  }
}

Translate to SQL as

CAST([p0].[State] ^ N''S'' AS bit) AS [Paid]

EF Core version: 9.0.0
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 8.0
Operating system: Windows 11 10.0.22631
IDE: Visual Studio 2022 17.11.6

@roji
Copy link
Member

roji commented Nov 13, 2024

Confirmed regression from 8.0 to 9.0; the new boolean XOR logic likely needs to be applied only when the store type is bit (whereas here we have a value converter).

For the following query with a value-converted enum, we now generate incorrect SQL with XOR logic (with a regular non-converted string the SQL is OK):

_ = await context.Orders
    .Select(order => new { Paid = order.State == OrderState.Paid })
    // The following is OK (non-value-converted string)
    // .Select(order => new { IsNameFoo = order.Name == "Foo" })
    .ToListAsync();

9.0 SQL:

SELECT ~CAST([o].[State] ^ N'Paid' AS bit) AS [Paid]
FROM [Orders] AS [o]

8.0 SQL:

SELECT CASE
    WHEN [o].[State] = N'Paid' THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END AS [Paid]
FROM [Orders] AS [o]
Full repro
await using var context = new BlogContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

_ = await context.Orders
    .Select(order => new { Paid = order.State == OrderState.Paid })
    // The following is OK (non-value-converted string)
    // .Select(order => new { IsNameFoo = order.Name == "Foo" })
    .ToListAsync();

public class BlogContext : DbContext
{
    public DbSet<Order> Orders { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer("Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0;Encrypt=false")
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Order>().Property(o => o.State).HasConversion<OrderStateConverter>();
    }

    private class OrderStateConverter() : ValueConverter<OrderState, string>(
        os => os.ToString(),
        s => Enum.Parse<OrderState>(s));
}

public class Order
{
    public int Id { get; set; }
    public OrderState State { get; set; }
    public string Name { get; set; }
}

public enum OrderState { Unpaid, Paid }

/cc @ranma42 - let me know if this is something you want to take a look at (and have time), as you worked on the original change.

@ranma42
Copy link
Contributor

ranma42 commented Nov 13, 2024

I will try and tackle this next weekend (aka it's unlikely I will have a PR ready before next week).

(the same issue is likely also affecting the ~ operator)

@ChrisJollyAU
Copy link
Contributor

@ranma42
Copy link
Contributor

ranma42 commented Nov 13, 2024

@ChrisJollyAU they are fine in that they support the operators (^ and ~), but they might not match the expected Boolean semantics.
For example one could have mapped true to 2 and false as 4, in which case a ^ b would result in a value that is neither true/2 nor false/4 (same goes for ~).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants