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

Invalid SQL generated when using Null Coalescing Operator on bool for the SQLServer provider #27176

Open
alaatm opened this issue Jan 12, 2022 · 4 comments
Labels
area-query consider-for-current-release customer-reported punted-for-7.0 Originally planned for the EF Core 7.0 (EF7) release, but moved out due to resource constraints. type-bug
Milestone

Comments

@alaatm
Copy link
Contributor

alaatm commented Jan 12, 2022

Given the following

public class Vehicle
{
    public int Id { get; set; }
    public VehicleRegistration Registration { get; set; } = default!;
}
public class VehicleRegistration
{
    public int Id { get; set; }
    public bool? Approved { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Vehicle> Vehicles { get; set; } = default!;

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder
        //.UseSqlite("Data Source=tst.db;");
        .UseSqlServer(@"Server=.;Database=EFCoreIssueNullableBool;Trusted_Connection=True;MultipleActiveResultSets=true");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Vehicle>()
            .HasOne(p => p.Registration)
            .WithOne()
            .HasForeignKey<VehicleRegistration>("vehicle_id")
            .IsRequired();
    }
}

When I execute the following query:

db.Vehicles.Where(p => !p.Registration.Approved ?? true);

The generated sql is wrong:

SELECT [v].[Id]
FROM [Vehicles] AS [v]
LEFT JOIN [VehicleRegistration] AS [v0] ON [v].[Id] = [v0].[vehicle_id]
WHERE COALESCE(CASE
    WHEN [v0].[Approved] = CAST(0 AS bit) THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END, CAST(1 AS bit)) = CAST(1 AS bit)

The second argument of the COALESCE function will never be picked even if [v0].[Approved] is null due to the CASE WHEN ... THEN ... ELSE ... END statement always evaluating to a non-null value.

The Sqlite provider generates a good sql that works:

SELECT "v"."Id"
FROM "Vehicles" AS "v"
LEFT JOIN "VehicleRegistration" AS "v0" ON "v"."Id" = "v0"."vehicle_id"
WHERE COALESCE(NOT ("v0"."Approved"), 1)

As a workaround, the query can be written as:

db.Vehicles.Where(p => p.Registration.Approved == null || !p.Registration.Approved.Value));

Where the following good sql is produced:

SELECT [v].[Id]
FROM [Vehicles] AS [v]
LEFT JOIN [VehicleRegistration] AS [v0] ON [v].[Id] = [v0].[vehicle_id]
WHERE ([v0].[Approved] IS NULL) OR ([v0].[Approved] = CAST(0 AS bit))

A working runnable repro is available here: https://github.com/alaatm/EFCoreIssueNullableBool

EF Core version: 6.0.1
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: (e.g. .NET 6.0.1)

@alaatm alaatm changed the title Invalid SQL generated when using Null Coalescing Operator for the SQLServer provider Invalid SQL generated when using Null Coalescing Operator on bool for the SQLServer provider Jan 12, 2022
@alaatm
Copy link
Contributor Author

alaatm commented Jan 12, 2022

Interestingly, this works too:

db.Vehicles.Where(p => !(p.Registration.Approved ?? false))

generating:

SELECT [v].[Id]
FROM [Vehicles] AS [v]
LEFT JOIN [VehicleRegistration] AS [v0] ON [v].[Id] = [v0].[vehicle_id]
WHERE COALESCE([v0].[Approved], CAST(0 AS bit)) = CAST(0 AS bit)

@ajcvickers ajcvickers added this to the 7.0.0 milestone Jan 14, 2022
@ajcvickers ajcvickers assigned smitpatel and unassigned maumar Aug 29, 2022
@smitpatel smitpatel removed this from the 7.0.0 milestone Sep 6, 2022
@smitpatel smitpatel removed their assignment Sep 6, 2022
@smitpatel
Copy link
Contributor

We don't really process !NULL as possible NULL we try to convert it to 2-value logic before applying ! operator. @maumar will have better idea how null semantics interact with this structure.

@ajcvickers ajcvickers added the punted-for-7.0 Originally planned for the EF Core 7.0 (EF7) release, but moved out due to resource constraints. label Sep 9, 2022
@ajcvickers ajcvickers added this to the Backlog milestone Sep 9, 2022
@edmacdonald
Copy link

I ran into this issue today, almost 2 years and a major version later (v7.0.11). If fixing it is not a priority, the framework should at least fail spectacularly when this syntax is used to avoid insidious, hard to find bugs.

@ranma42
Copy link
Contributor

ranma42 commented Jun 28, 2024

This seems to be a special case of #34001

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-query consider-for-current-release customer-reported punted-for-7.0 Originally planned for the EF Core 7.0 (EF7) release, but moved out due to resource constraints. type-bug
Projects
None yet
Development

No branches or pull requests

7 participants