-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Closed as duplicate of#33757
Closed as duplicate of#33757
Copy link
Labels
Description
I'm not sure whether the following sematics of .All() are by design as EF Core's behaviour differs from LINQs behaviour:
E.g. SomeCollection.All(x => x.NullableInt > 2) returns true in EF Core and false in LINQ if any of the NullableInt values is null, if all non-null values satisfy the condition. If all NullableInt values are null, in EF Core's semantics this also means SomeCollection.All(x => x.NullableInt > 2) == true == SomeCollection.All(x => x.NullableInt <= 2).
Code
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
const string ConnectionString = @"Data Source=InMemorySample;Mode=Memory;Cache=Shared";
await using var keepOpenCon = new SqliteConnection(ConnectionString);
await keepOpenCon.OpenAsync();
await using var ctx = new ReproContext(ConnectionString);
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
Console.WriteLine(ctx.Database.GenerateCreateScript());
Console.WriteLine("----");
var lst = new List<Blog> {
new() { Name = "FooBlog 1", Rating = null, IsNice = null },
new() { Name = "FooBlog 2", Rating = 2, IsNice = true },
new() { Name = "FooBlog 3", Rating = null, IsNice = null },
new() { Name = "FooBlog 4", Rating = 2, IsNice = true },
};
ctx.Blogs.AddRange(lst);
await ctx.SaveChangesAsync();
Console.WriteLine(JsonSerializer.Serialize(lst));
Console.WriteLine("----");
Console.WriteLine("-- Expected NULL Propagation with int? and == --");
var resp = await ctx.Blogs.AllAsync(b => b.Rating == 2);
Console.WriteLine(JsonSerializer.Serialize(resp));
Console.WriteLine();
Console.WriteLine("-- Unexpected NULL Propagation with int? and > --");
var resp1 = await ctx.Blogs.AllAsync(b => b.Rating >= 1); // wouldn't one expect this to be false, no matter which variant of <, <=, >, >= is used with whatever value?
Console.WriteLine(JsonSerializer.Serialize(resp1));
Console.WriteLine();
Console.WriteLine("-- Local LINQ Evaluation --");
Console.WriteLine(JsonSerializer.Serialize(lst.All(b => b.Rating >= 1)));
Console.WriteLine();
Console.WriteLine("-- Manual Workaround NULL Check in Queryable --");
var resp2 = await ctx.Blogs.AllAsync(b => b.Rating.HasValue && b.Rating >= 1);
Console.WriteLine(JsonSerializer.Serialize(resp2));
Console.WriteLine();
Console.WriteLine("-- Unexpected NULL Propagation with bool? --");
var resp3 = await ctx.Blogs.AllAsync(b => b.IsNice!.Value); // okay, I used dammit here, but maybe one would expect this to be the same as below?
Console.WriteLine(JsonSerializer.Serialize(resp3));
Console.WriteLine();
Console.WriteLine("-- Expected NULL Propagation with bool? --");
var resp4 = await ctx.Blogs.AllAsync(b => b.IsNice == true);
Console.WriteLine(JsonSerializer.Serialize(resp4));
public class ReproContext(string ConnectionString) : DbContext
{
public DbSet<Blog> Blogs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlite(ConnectionString).LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information, Microsoft.EntityFrameworkCore.Diagnostics.DbContextLoggerOptions.None);
}
public class Blog
{
public int Id { get; set; }
public required string Name { get; set; }
public required int? Rating { get; set; }
public required bool? IsNice { get; set; }
}<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
</ItemGroup>
</Project>Output
...
-- Expected NULL Propagation with int? and == --
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT NOT EXISTS (
SELECT 1
FROM "Blogs" AS "b"
WHERE "b"."Rating" <> 2 OR "b"."Rating" IS NULL)
false
-- Unexpected NULL Propagation with int? and > --
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT NOT EXISTS (
SELECT 1
FROM "Blogs" AS "b"
WHERE NOT ("b"."Rating" >= 1))
true
-- Local LINQ Evaluation --
false
-- Manual Workaround NULL Check in Queryable --
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT NOT EXISTS (
SELECT 1
FROM "Blogs" AS "b"
WHERE "b"."Rating" IS NULL OR "b"."Rating" < 1)
false
-- Unexpected NULL Propagation with bool? --
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT NOT EXISTS (
SELECT 1
FROM "Blogs" AS "b"
WHERE NOT ("b"."IsNice"))
true
-- Expected NULL Propagation with bool? --
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT NOT EXISTS (
SELECT 1
FROM "Blogs" AS "b"
WHERE "b"."IsNice" = 0 OR "b"."IsNice" IS NULL)
false
After reading Query null semantics, I wasn't sure if the above behaviour is expected or if anything of that is unexpected/a bug?