数据库并发_指多个进程或用户同时访问或更改数据库中的相同数据的情况。 并发控制指的是用于在发生并发更改时确保数据一致性的特定机制。
EF Core 实现乐观并发控制,这意味着它将允许多个进程或用户独立进行更改而不产生同步或锁定的开销。 在理想情况下,这些更改将不会相互干扰,因此都能够成功。 在最坏的情况下,两个或更多进程将尝试进行冲突更改,其中只有一个进程应该成功。
并发控制在 EF Core 中的工作原理
配置为并发令牌的属性用于实现乐观并发控制:每当在 SaveChanges 期间执行更新或删除操作时,会将数据库上的并发令牌值与通过 EF Core 读取的原始值进行比较。
如果这些值匹配,则可以完成该操作。
如果这些值不匹配,EF Core 会假设另一个用户已执行冲突操作,并中止当前事务。
另一个用户已执行与当前操作冲突的操作的情况称为并发冲突。
数据库提供程序负责实现并发令牌值的比较。
并发标记
配置为并发标记的属性用于实现乐观并发控制。
public class Person
{
public int PersonId { get; set; }
[ConcurrencyCheck]
public string LastName { get; set; }
public string FirstName { get; set; }
}Code language: JavaScript (javascript)
或者
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.Property(p => p.LastName)
.IsConcurrencyToken();
}Code language: JavaScript (javascript)
Timestamp/rowversion
Timestamp/rowversion 是一个属性,在每次插入或更新行时,数据库会自动为其生成新值。 此属性也被视为并发标记,这确保了在你查询行后,如果正在更新的行发生了更改,则会出现异常。 确切的详细信息取决于所使用的数据库提供程序;对于 SQL Server,通常使用byte [] 属性,该属性将设置为数据库中的ROWVERSION列。
可以按如下所示将属性配置为 timestamp/rowversion:
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
[Timestamp]
public byte[] Timestamp { get; set; }
}Code language: JavaScript (javascript)
或者
class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(p => p.Timestamp)
.IsRowVersion();
}
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public byte[] Timestamp { get; set; }
}Code language: HTML, XML (xml)
冲突的解决
在关系数据库上,EF Core 包括对任何 UPDATE 或 DELETE 语句的 WHERE 子句中的并发令牌值的检查。 执行这些语句后,EF Core 会读取受影响的行数。
如果未影响任何行,将检测到并发冲突,并且 EF Core 会引发 DbUpdateConcurrencyException。
例如,我们可能希望将 Person 上的 LastName 配置为并发令牌。 则针对用户的任何更新操作将包括 WHERE 子句中的并发检查:
具体看
https://docs.microsoft.com/zh-cn/ef/core/saving/concurrency
using (var context = new PersonContext())
{
// Fetch a person from database and change phone number
var person = context.People.Single(p => p.PersonId == 1);
person.PhoneNumber = "555-555-5555";
// Change the person's name in the database to simulate a concurrency conflict
context.Database.ExecuteSqlRaw(
"UPDATE dbo.People SET FirstName = 'Jane' WHERE PersonId = 1");
var saved = false;
while (!saved)
{
try
{
// Attempt to save changes to the database
context.SaveChanges();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Person)
{
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];//当前值
var databaseValue = databaseValues[property];//数据库中的值
// TODO: 决定应将哪个值写入数据库
// proposedValues[property] = <value to be saved>;
}
// 刷新原始值以绕过下一次并发检查
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
}
}Code language: PHP (php)