Skip to content

Inconsistent results for Nullable<bool>.ToString() #33941

@ranma42

Description

@ranma42

The result of the translation of Nullable<bool>.ToString() is different depending on whether the method is invoked on a column or another type of expression.

When invoked on a null in a column, the result is null, while doing the same on an expression returns the string "True" (even though the value is null).

An example program that showcases the bug is:

using System;
using System.Data;
using System.Linq;
using Microsoft.EntityFrameworkCore;

using var db = new BloggingContext();

db.Database.EnsureDeleted();
db.Database.EnsureCreated();

var bools = db.Blogs
	.Select(x =>  x.NullableBool)
    .Select(x => new { Raw = x, String = x.ToString() })
    .ToList();

foreach (var x in bools) {
	Console.WriteLine($"'{x.Raw.ToString() ?? "-"}' -> '{x.String ?? "-"}'");
}

bools = db.Blogs
	.Select(x => x.NullableBool & (x.BlogId > 0))
    .Select(x => new { Raw = x, String = x.ToString() })
    .ToList();

foreach (var x in bools) {
	Console.WriteLine($"'{x.Raw.ToString() ?? "-"}' -> '{x.String ?? "-"}'");
}

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options
            .LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information)
            .UseSqlite($"Data Source=test.db");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
		modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, NullableBool = false });
		modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 2, NullableBool = true });
		modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 3, NullableBool = null });
    }
}

public class Blog
{
    public int BlogId { get; set; }
	public bool? NullableBool { get; set; }
}

The first query is translated to

      SELECT "b"."NullableBool" AS "Raw", CASE
          WHEN "b"."NullableBool" = 0 THEN 'False'
          WHEN "b"."NullableBool" = 1 THEN 'True'
          ELSE NULL
      END AS "String"
      FROM "Blogs" AS "b"

hence the command writes

'False' -> 'False'
'True' -> 'True'
'' -> '-'

(notation: value converted to string in C# -> value converted to string by EFCore; '-' is used as a marker for null)

The second query is translated to

      SELECT "b"."NullableBool" & ("b"."BlogId" > 0) AS "Raw", CASE
          WHEN "b"."NullableBool" & ("b"."BlogId" > 0) = 0 THEN 'False'
          ELSE 'True'
      END AS "String"
      FROM "Blogs" AS "b"

hence the program writes

'False' -> 'False'
'True' -> 'True'
'' -> 'True'

Include provider and version information

EF Core version: 8.0.6
Database provider: Microsoft.EntityFrameworkCore.Sqlite
Target framework: .NET 8.0
Operating system: Linux (/WSL)
IDE: Visual Studio Code 1.89.1

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions