dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.8k stars 3.2k forks source link

ExecuteUpdate on Nullable stop working after migration from 8 to 9 #35164

Open vzabavnov opened 1 day ago

vzabavnov commented 1 day ago

the test:

public enum Status { Good, NotGood }

public class Entity
{
    public int ID { get; set; }

    public string Value { get; set; } = null!;

    public Status? Status { get; set; }
}

public class Context(DbContextOptions<Context> options) : DbContext(options)
{
    public DbSet<Entity> Entities { get; set; }

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

        builder.Entity<Entity>(entity =>
        {
            entity.HasKey(z => z.ID);
            entity.HasData(new Entity() { ID = 1, Value = "Value 1" });
        });
    }
}

public class UnitTest
{
    [Fact]
    public void Test()
    {
        var connection = new SqliteConnection("datasource=:memory:");
        connection.Open();
        var options = new DbContextOptionsBuilder<Context>().UseSqlite(connection).Options;

        using (var ctx = new Context(options))
        {
            ctx.Database.EnsureCreated();

            Assert.Single(ctx.Entities);
            Assert.Contains(ctx.Entities, z => z.Value == "Value 1");
            Assert.DoesNotContain(ctx.Entities, z => z.Status != null);
        }

        using (var ctx = new Context(options))
        {
            ctx.Entities.ExecuteUpdate(u => u.SetProperty(z => z.Status, _ => Status.Good));
        }

        using (var ctx = new Context(options))
        {
            Assert.True(ctx.Entities.All(z => z.Status == Status.Good));
        }
    }
}

Works fine in all 8s versions.

In 9.0 it gives exception:

System.InvalidOperationException
No coercion operator is defined between types 'System.Func`2[EfCore9Bug.Entity,System.Nullable`1[EfCore9Bug.Status]]' and 'EfCore9Bug.Status'.

The work around is declare variable with Status outside of the ExecuteUpdate and cast it to Nullable:


using (var ctx = new Context(options))
{
    var status = (Status?)Status.Good;
    ctx.Entities.ExecuteUpdate(u => u.SetProperty(z => z.Status, _ => status));
}