Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to handle optimistic concurrency with Entity Framework 7

I am using ASP.NET 5 rc1 and Entity 7 rc1 over coreclr with a sample code first-migration project.

I was having a look at how to manage concurrency in entities but I couldn't find any good updated information about the recommended concurrency handling practices for Entity 7.

I have a few entities, they all implement the interface:

public interface IVersionedModel
{
    byte [] RowVersion { get; set; }
}

as for example in:

public class User : IVersionedModel
{
    public Guid UserId { get; set; }
    public string Name { get; set; }
    public byte[] RowVersion { get; set; }
}

Instead adding the attribute [Timestamp] to each RowVersion column I prefer to use the fluent configuration, so in my DbContext I specify for each entity that I want that column to be handled as the row version.

However Entity 7 does not seem to support anymore the option .IsRowVersion() but it has the option IsConcurrencyToken(), which should suffice but in code first migrations it generates a nullable varbinary column and when inserting/updating rows it does not auto-increment.

I wonder whether the best thing to do is to explicitly manage the version of each row by increasing the version when updating or inserting entities in the database.. This is my DbContext

public class AppDbContext : DbContext
{
    public AppDbContext() : base()
    {
    }

    public DbSet<User> Users { get; set; }
    public DbSet<Organization> Organizations { get; set; }
    public DbSet<Membership> Memberships { get; set; }

    public override int SaveChanges()
    {
        //increase entity version for concurrency purposes
        foreach(var dbEntityEntry in ChangeTracker.Entries()
            .Where(x => x.State == EntityState.Added || x.State == EntityState.Modified))
        {
            IVersionedModel entity = dbEntityEntry.Entity as IVersionedModel;
            if(entity != null)
            {
                //Increase byte[] RowVersion?
            }
        }
        return base.SaveChanges();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>().Property(u => u.RowVersion).IsConcurrencyToken();
        modelBuilder.Entity<Organization>().Property(o => o.RowVersion).IsConcurrencyToken();
        modelBuilder.Entity<Membership>().Property(m => m.RowVersion).IsConcurrencyToken();
    }
}

Basically my question is, how to handle concurrency with ASP.NET 5 and Entity 7? It's the first time I deal with this thing and any suggestion will be appreciated.

UPDATE: Thanks to the link https://stackoverflow.com/users/1922568/joe-audette provided I found some answers, unfortunately not all

As per http://docs.efproject.net/en/latest/modeling/concurrency.html#how-concurrency-tokens-work-in-ef it seems we can use Timestamp annotation if we want to version a row or ConcurrencyCheck annotation on a property if we want to version based on a property (not the whole row).

The fluent API only allows to configure concurrency token by using .IsConcurrencyToken()

I haven't found yet a way through the fluent API to version the whole row with a dedicated column (it'd be nice if the fluent API allowed an .IsRowVersion(), but it doesn't)

like image 432
diegosasw Avatar asked Oct 18 '22 21:10

diegosasw


1 Answers

I've created a static class to handle this and other missing features of EF7 ModelBuilder (use with caution, not tested in a production environment)

public static class SqlServerModelBuilderExtensions
{
    public static PropertyBuilder<decimal?> HasPrecision(this PropertyBuilder<decimal?> builder, int precision, int scale)
    {
        return builder.HasColumnType($"decimal({precision},{scale})");
    }

    public static PropertyBuilder<decimal> HasPrecision(this PropertyBuilder<decimal> builder, int precision, int scale)
    {
        return builder.HasColumnType($"decimal({precision},{scale})");
    }

    public static PropertyBuilder<byte[]> IsRowVersion(this PropertyBuilder<byte[]> builder)
    {
        return builder.HasColumnType("rowversion").IsConcurrencyToken().ValueGeneratedOnAdd();
    }
}
like image 174
Andrés Robinet Avatar answered Dec 23 '22 05:12

Andrés Robinet