通常,可以使用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')
这是带有DbContext
和Record
实体的代码:
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'
。
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如何自动where
在update
语句的子句中包括为并发控制定义的列。
在这种情况下,因为实际上存在并发更新,EF会检测到它,并DbUpdateConcurrencyException
在此行上引发异常:
context.SaveChanges();
因此,在这种情况下,如果您检查数据库,您将看到有State
问题的行的值为'Sneaky Update'
,因为我们的第二次更新未能通过并发检查。
但是,它变得棘手的地方是,如何DbUpdateConcurrencyException
在抛出异常时处理异常?在这种情况下,完全由您决定要做什么。但是,有关该主题的进一步指导,您可以在这里找到更多信息:EF- 乐观并发模式。