EF Core并发冲突处理方式


前言

介绍EF Core如何处理多个用户同时更新同一实体数据时出现的冲突。


一、并发冲突的处理方式

1. 悲观并发(锁定)

在从数据库读取一行内容之前,请求锁定为只读或更新访问
如果将一行锁定为更新访问,则其他用户无法将该行锁定为只读或更新访问,因为他们得到的是正在更改的数据的副本。
如果将一行锁定为只读访问,则其他人也可将其锁定为只读访问,但不能进行更新。
缺点: 编程复杂; 性能消耗; 有的数据库管理系统不支持; Entity Framework Core 未提供对它的内置支持.

2. 乐观并发

乐观并发允许发生并发冲突,并在并发冲突发生时作出正确反应.

本文只介绍乐观并发的处理方式

二、乐观并发的处理步骤

按照并发冲突检测 -> 触发异常 -> 处理异常 的步骤进行处理.
在关系数据库中,EF Core 会从 UPDATE 和 DELETE 语句的 WHERE 子句中查看并发标记的值,以检测并发冲突。
因此必须将模型配置为启用冲突检测, EF 提供两种使用并发标记的方法:

1.在模型的属性上应用并发标记(不推荐)

将 [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] = <value to be saved>;
                        // 将要写入数据库中的值赋给proposedValues[property]
                    }
                    // 刷新实体的原始值以进行下一次并发冲突检查
                    entry.OriginalValues.SetValues(databaseValues);
                }
                else
                {
                    throw new NotSupportedException(
                        "Don't know how to handle concurrency conflicts for "
                        + entry.Metadata.Name);
                }
            }
        }
    }
}

2.在模型中新增时间戳作为并发标记(推荐)

新增属性用[Timestamp]标记
代码如下(示例):

// 实体模型定义------------------------------------
[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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值