简体   繁体   中英

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. 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.

I noticed that "normal" update procedure (just getting the entity, changing properties and saving) does not respect the RowVersion expected behavior. But if I get the entity using AsNoTracking and use the db.Update method, the concurrency check works as expected. What could be the reason, and is the db.Update the only way to force the RowVersion check? 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:

    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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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