Description
After upgrading to .NET 9 and EF Core, I noticed a change in SQL translation for Boolean comparisons. Specifically, EF Core now uses bitwise operations (~ and ^) instead of a CASE statement. This leads to incorrect results when either operand in the comparison is NULL.
Expected Behavior
EF Core should generate SQL that correctly handles NULL values in Boolean comparisons, as was the case in earlier versions, where it used a CASE statement.
Example of the previous SQL generation:
SELECT CASE
WHEN [p].[PayeeId] = [p].[Payerd] THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS [IsPayerSameAsPayee]
Actual Behavior
After upgrading, EF Core generates SQL using bitwise operations. If either operand is NULL, the result of the operation is NULL, causing the Boolean field in the application to have unexpected values.
Example of the new SQL generation:
SELECT ~CAST([p].[PayeeId] ^ [p].[Payerd] AS bit) AS [IsPayerSameAsPayee]
This behavior causes IsPayerSameAsPayee to be NULL instead of TRUE or FALSE.
Steps to Reproduce
Code
Here are two examples that reproduce the issue:
- Boolean Comparison Between Two Fields
public Task<PaymentReceiptDataModel> GetPaymentReceiptModelAsync(long paymentId) =>
UnitOfWork.Query<Payment>(p => p.Id == paymentId)
.Select(payment => new PaymentReceiptDataModel
{
IsPayerSameAsPayee = payment.PayeeId== payment.Payerd, // PayeeId or Payerd are nullable here
//... Some others attributes..
})
.FirstOrDefaultAsync();
- Boolean Comparison with Enum
public Task<PlanDataModel> GetInstallmentPlanStatusAsync(long paymentItemId) =>
UnitOfWork.Query<PaymentItem>(p => p.Id == paymentItemId)
.Select(paymentItem => new PlanDataModel
{
IsActive= paymentItem.SomeObjectThatCanBeNullable.Status== PlanStatus.Active,
})
.FirstOrDefaultAsync();
SQL Translation Before Upgrade ( was returning true or false always )
SELECT CASE
WHEN [p].[PayeeId] = [p].[Payerd] THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS [IsPayerSameAsPayee]
FROM [Payment].[Payment] AS [p]
WHERE [p].[Id] = @paymentId
SQL Translation After Upgrade ( is returning null when either operand in the comparison is NULL )
SELECT ~CAST([p].[PayeeId] ^ [p].[Payerd] AS bit) AS [IsPayerSameAsPayee]
FROM [Payment].[Payment] AS [p]
WHERE [p].[Id] = @paymentId
Environment Information
EF Core version: EF Core for .NET 9
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 9.0
Operating system: Windows 11
IDE: Visual Studio 2022 17.8
Stack Trace
The issue causes the application to throw a System.InvalidOperationException due to the mismatch between the SQL result and the expected C# data type. Since IsPayerSameAsPayee is a non-nullable bool, but the SQL translation can return NULL when either operand in the comparison is NULL, EF Core attempts to assign the NULL result to the bool property, resulting in the following exception:
System.InvalidOperationException: Nullable object must have a value.
This occurs because C# does not allow a null value to be assigned to a bool without explicitly handling it as a nullable type.
Additional Notes
This change in SQL translation does not appear in the breaking change documentation for EF Core, and after upgrading, lots of stuff started to fail a part , so I'm expecting it's a bug now
Description
After upgrading to .NET 9 and EF Core, I noticed a change in SQL translation for Boolean comparisons. Specifically, EF Core now uses bitwise operations (
~and^) instead of aCASEstatement. This leads to incorrect results when either operand in the comparison isNULL.Expected Behavior
EF Core should generate SQL that correctly handles
NULLvalues in Boolean comparisons, as was the case in earlier versions, where it used aCASEstatement.Example of the previous SQL generation:
Actual Behavior
After upgrading, EF Core generates SQL using bitwise operations. If either operand is
NULL, the result of the operation isNULL, causing the Boolean field in the application to have unexpected values.Example of the new SQL generation:
This behavior causes
IsPayerSameAsPayeeto beNULLinstead ofTRUEorFALSE.Steps to Reproduce
Code
Here are two examples that reproduce the issue:
SQL Translation Before Upgrade ( was returning true or false always )
SQL Translation After Upgrade ( is returning null when either operand in the comparison is
NULL)Environment Information
EF Core version: EF Core for .NET 9
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 9.0
Operating system: Windows 11
IDE: Visual Studio 2022 17.8
Stack Trace
The issue causes the application to throw a
System.InvalidOperationExceptiondue to the mismatch between the SQL result and the expected C# data type. SinceIsPayerSameAsPayeeis a non-nullablebool, but the SQL translation can returnNULLwhen either operand in the comparison isNULL, EF Core attempts to assign theNULLresult to theboolproperty, resulting in the following exception:This occurs because C# does not allow a
nullvalue to be assigned to aboolwithout explicitly handling it as a nullable type.Additional Notes
This change in SQL translation does not appear in the breaking change documentation for EF Core, and after upgrading, lots of stuff started to fail a part , so I'm expecting it's a bug now