简体   繁体   English

EF Core 6“正常”更新方法不尊重 RowVersion 预期行为?

[英]EF Core 6 "normal" update method doesn't respect RowVersion expected behavior?

I have a .NET6 API project that allows users to fetch resources from a database (SQL Server), and update them on a web client, and submit the updated resource back for saving to db.我有一个 .NET6 API 项目,它允许用户从数据库(SQL Server)中获取资源,并在 Web 客户端上更新它们,然后提交更新的资源以保存到 db。 I need to notify users if another user has already updated the same resource during editing.如果另一个用户在编辑期间已经更新了相同的资源,我需要通知用户。 I tried using EF IsRowVersion property for this concurrency check.我尝试使用 EF IsRowVersion 属性进行此并发检查。

I noticed that "normal" update procedure (just getting the entity, changing properties and saving) does not respect the RowVersion expected behavior.我注意到“正常”更新过程(只是获取实体、更改属性和保存)不尊重 RowVersion 预期行为。 But if I get the entity using AsNoTracking and use the db.Update method, the concurrency check works as expected.但是,如果我使用 AsNoTracking 获取实体并使用 db.Update 方法,并发检查将按预期工作。 What could be the reason, and is the db.Update the only way to force the RowVersion check?可能是什么原因,db.Update 是强制 RowVersion 检查的唯一方法吗? That method has the downside that it tries to update every property, not just those that have changed.该方法的缺点是它会尝试更新每个属性,而不仅仅是那些已更改的属性。 Simplified and runnable console app example below:下面是简化且可运行的控制台应用程序示例:

using Microsoft.EntityFrameworkCore;
Guid guid;
using (PeopleContext db = new())
{
    Person p = new() { Name = "EF", Age = 30 };
    db.Database.EnsureDeleted();
    db.Database.EnsureCreated();
    db.People.Add(p);
    await db.SaveChangesAsync();
    guid = p.Id;
}
using (PeopleContext db = new())
{
    Person p = await db.People.FirstAsync(x => x.Id == guid);
    p.Name = "FE";
    p.RowVersion = Convert.FromBase64String("AAAAAADDC9I=");
    await db.SaveChangesAsync(); // Does not throw even though RowVersion is incorrect
}
using (PeopleContext db = new())
{
    Person p = await db.People.AsNoTracking().FirstAsync(x => x.Id == guid);
    p.Name = "EFFE";
    p.RowVersion = Convert.FromBase64String("AAAAAAGGC9I=");
    db.People.Update(p);
    await db.SaveChangesAsync(); // Throws DbUpdateConcurrencyException as expected, but updates all properties
}
public class Person
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public int Age { get; set; }
    public byte[] RowVersion { get; set; } = Array.Empty<byte>();
}

public class PeopleContext : DbContext
{
    public PeopleContext(){}

    public DbSet<Person> People => Set<Person>();
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=EFRowVersionDb;Integrated Security=True;");
        optionsBuilder.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);
        optionsBuilder.EnableSensitiveDataLogging();

    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>(entity =>
        {
            entity.Property(e => e.RowVersion)
                .IsRequired()
                .IsRowVersion();
        });
    }
}

I solved the problem by overriding the SaveChangesAsync method like this:我通过覆盖这样的 SaveChangesAsync 方法解决了这个问题:

    public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
    {
        foreach (var item in ChangeTracker.Entries().Where(x=>x.State == EntityState.Modified))
        {
            item.OriginalValues["RowVersion"] = item.CurrentValues["RowVersion"];
        }
        return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
    }

I override that signature method cause the one without boolean calls that method.我重写了那个签名方法,导致没有布尔值的那个方法调用了那个方法。 Same thing on sync version.同步版本也是一样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM