您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

使用实体框架进行原子读写

使用实体框架进行原子读写

通常,可以使用2种主要的并发模式:

以EF支持的开放式并发选项为重点,让我们比较一下在有和没有EF进行开放式并发控制处理的情况下示例的行为。我假设您正在使用sql Server。

让我们从数据库中的以下脚本开始:

create table Record (
  Id int identity not null primary key,
  State varchar(50) not null
)

insert into Record (State) values ('Initial')

这是带有DbContextRecord实体的代码

public class MyDbContext : DbContext
{
    static MyDbContext()
    {
        Database.SetInitializer<MyDbContext>(null);
    }

    public MyDbContext() : base(@"Server=localhost;Database=eftest;Trusted_Connection=True;") { }

    public DbSet<Record> Records { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Configurations.Add(new Record.Configuration());
    }
}

public class Record
{
    public int Id { get; set; }

    public string State { get; set; }

    public class Configuration : EntityTypeConfiguration<Record>
    {
        public Configuration()
        {
            this.HasKey(t => t.Id);

            this.Property(t => t.State)
                .HasMaxLength(50)
                .Isrequired();
        }
    }
}

现在,让我们使用以下代码测试并发更新方案:

static void Main(string[] args)
{
    using (var context = new MyDbContext())
    {
        var record = context.Records
            .Where(r => r.Id == 1 && r.State == "Initial")
            .Single();

        // Insert sneaky update from a different context.
        using (var sneakyContext = new MyDbContext())
        {
            var sneakyRecord = sneakyContext.Records
                .Where(r => r.Id == 1 && r.State == "Initial")
                .Single();

            sneakyRecord.State = "Sneaky Update";
            sneakyContext.SaveChanges();
        }

        // attempt to update row that has just been updated and committed by the sneaky context.
        record.State = "Second";
        context.SaveChanges();
    }
}

如果跟踪sql,您将看到该update语句如下所示:

UPDATE [dbo].[Record]
SET [State] = 'Second'
WHERE ([Id] = 1)

因此,实际上,它不关心其他事务是否潜入了更新。它只是盲目地写了其他更新所做的任何事情。因此,该State行在数据库中的最终值是'Second'

让我们调整初始sql脚本,以在表中包括一个并发控制列:

create table Record (
  Id int identity not null primary key,
  State varchar(50) not null,
  Concurrency timestamp not null -- add this row versioning column
)

insert into Record (State) values ('Initial')

让我们还调整我们的Record实体类DbContext类保持不变):

public class Record
{
    public int Id { get; set; }

    public string State { get; set; }

    // Add this property.
    public byte[] Concurrency { get; set; }

    public class Configuration : EntityTypeConfiguration<Record>
    {
        public Configuration()
        {
            this.HasKey(t => t.Id);

            this.Property(t => t.State)
                .HasMaxLength(50)
                .Isrequired();

            // Add this config to tell EF that this
            // property/column should be used for 
            // concurrency checking.
            this.Property(t => t.Concurrency)
                .IsRowVersion();
        }
    }
}

现在,如果我们尝试重新运行Main()用于上一场景的相同方法,您将注意到该update语句的生成和执行方式发生了变化:

UPDATE [dbo].[Record]
SET [State] = 'Second'
WHERE (([Id] = 1) AND ([Concurrency] = <byte[]>))
SELECT [Concurrency]
FROM [dbo].[Record]
WHERE @@ROWCOUNT > 0 AND [Id] = 1

特别要注意的是,EF如何自动whereupdate语句的子句中包括为并发控制定义的列。

在这种情况下,因为实际上存在并发更新,EF会检测到它,并DbUpdateConcurrencyException在此行上引发异常:

context.SaveChanges();

因此,在这种情况下,如果您检查数据库,您将看到有State问题的行的值为'Sneaky Update',因为我们的第二次更新未能通过并发检查。

如您所见,在EF中激活自动乐观并发控制不需要做太多的事情。

但是,它变得棘手的地方是,如何DbUpdateConcurrencyException在抛出异常时处理异常?在这种情况下,完全由您决定要做什么。但是,有关该主题的进一步指导,您可以在这里找到更多信息:EF- 乐观并发模式

其他 2022/1/1 18:36:43 有392人围观

撰写回答


你尚未登录,登录后可以

和开发者交流问题的细节

关注并接收问题和回答的更新提醒

参与内容的编辑和改进,让解决方法与时俱进

请先登录

推荐问题


联系我
置顶