- 前言
- 一、并发冲突的处理方式
- 1. 悲观并发(锁定)
- 2. 乐观并发
- 二、乐观并发的处理步骤
- 1.在模型的属性上应用并发标记(不推荐)
- 2.在模型中新增时间戳作为并发标记(推荐)
前言
介绍EF Core如何处理多个用户同时更新同一实体数据时出现的冲突。
一、并发冲突的处理方式 1. 悲观并发(锁定)
在从数据库读取一行内容之前,请求锁定为只读或更新访问
如果将一行锁定为更新访问,则其他用户无法将该行锁定为只读或更新访问,因为他们得到的是正在更改的数据的副本。
如果将一行锁定为只读访问,则其他人也可将其锁定为只读访问,但不能进行更新。
缺点: 编程复杂; 性能消耗; 有的数据库管理系统不支持; Entity Framework Core 未提供对它的内置支持.
乐观并发允许发生并发冲突,并在并发冲突发生时作出正确反应.
本文只介绍乐观并发的处理方式
二、乐观并发的处理步骤按照并发冲突检测 -> 触发异常 -> 处理异常 的步骤进行处理.
在关系数据库中,EF Core 会从 UPDATE 和 DELETE 语句的 WHERE 子句中查看并发标记的值,以检测并发冲突。
因此必须将模型配置为启用冲突检测, EF 提供两种使用并发标记的方法:
将 [ConcurrencyCheck] 应用于模型上的属性。
代码如下(示例):
// 实体模型定义------------------------------------
[Table("task_test")]
public class TaskTest {
public int Id { get; set; }
public string? Name { get; set; }
[ConcurrencyCheck] // 并发标记
public int Value { get; set; }
}
// 并发冲突处理代码--------------------------------------------------
using (var context = new TaskContext())
{
// 由数据库中取出一条数据
var task = context.TaskTests.Single(p => p.Id == 1);
// 直接修改数据库中的这条数据来模拟并发冲突
context.Database.ExecuteSqlRaw(
$"UPDATE task_test SET value = {task.Value + 1} WHERE Id = 1");
// 修改取出的数据值
task.Value = task.Value + 2;
var saved = false;
while (!saved)
{
try
{
// 尝试写入该条数据
context.SaveChanges();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
// 并发冲突异常处理
foreach (var entry in ex.Entries)
{
if (entry.Entity is TaskTest)
{
var proposedValues = entry.CurrentValues; // 准备写入的值
var databaseValues = entry.GetDatabaseValues(); // 数据库中的值
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
Console.WriteLine($"属性名: {property.Name} 准备写入: {proposedValue} 数据库中: {databaseValue}");
// TODO: 决定将哪个值写入数据库
// proposedValues[property] = ;
// 将要写入数据库中的值赋给proposedValues[property]
}
// 刷新实体的原始值以进行下一次并发冲突检查
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
}
}
2.在模型中新增时间戳作为并发标记(推荐)
新增属性用1651133190标记
代码如下(示例):
// 实体模型定义------------------------------------
[Table("task_row")]
public class TaskRow
{
public int Id { get; set; }
public string? Name { get; set; }
public int Value { get; set; }
[Timestamp] // 并发冲突检测标志
public byte[] RowVersion { get; set; }
}
// 并发冲突处理代码--------------------------------------------------
using var context = new TaskContext();
// 从数据库中取出一条数据
var taskRow = context.TaskRows.Single(p => p.Id == 1);
// 直接修改数据库中的这条数据来模拟并发冲突
context.Database.ExecuteSqlRaw(
$"UPDATE task_row SET value = {taskRow.Value + 1} WHERE Id = 1");
// 修改取出的数据值
taskRow.Value = taskRow.Value + 2;
var saved = false;
while (!saved)
{
try
{
await context.SaveChangesAsync();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValue = (TaskRow)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
var databaseValue = (TaskRow)databaseEntry.ToObject();
Console.WriteLine($"Proposed: {clientValue.Value} DB: {databaseValue.Value}");
// 更新实体的时间戳以进行下一次写入尝试, 也可以根据情况放弃写入
context.Entry(taskRow).Property("RowVersion").OriginalValue = (byte[])databaseValue.RowVersion;
}
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)