- A+
The database operation was expected to affect 1 row(s), but actually affected 0 row(s); 解决乐观并发
1.乐观并发
EF Core 实现 乐观并发,假定并发冲突相对较少。 与 悲观 方法(即先锁定数据,然后才继续修改数据)不同,乐观并发不需要锁定,而是安排数据修改在保存时失败(如果数据自查询后已更改)。 此并发故障将报告给应用程序,应用程序可能会通过对新数据重试整个操作来相应地处理它。
在 EF Core 中,乐观并发是通过将属性配置为 并发令牌来实现的。 在查询实体时加载和跟踪并发令牌,就像任何其他属性一样。 然后,在 期间 SaveChanges()执行更新或删除操作时,数据库上的并发令牌值与 EF Core 读取的原始值进行比较。
代码触发乐观会如下错误:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
2.设置乐观锁
我的程序使用EF Core,然后给实体类设置了乐观并发 UseIdentityColumn
public void Configure(EntityTypeBuilder<ChatRecord> builder) { //在 PostgreSQL 中,表名以及列名是不区分大小写的。这意味着在数据库中创建表时,无论您是使用大写、小写或混合大小写的名称,最终都会使用相同的名称来访问和操作该表。 //设置表 builder.ToTable("ChatRecord"); //设置表主键 builder.HasKey(e => e.ChatRecordId); //设置主键自增 builder.Property(e => e.ChatRecordId) .UseIdentityColumn(); }
3.什么情况下会触发乐观并发
乐观并发允许发生并发冲突,并在并发冲突发生时作出正确反应。 例如,Jane 访问院系编辑页面,将英语系的预算从 350,000.00 美元更改为 0.00 美元。
在 Jane 单击“保存”之前,John 访问了相同页面,并将开始日期字段从 2007/1/9 更改为 2013/1/9。
Jane 单击“保存”后看到更改生效,因为浏览器会显示预算金额为零的“索引”页面。
John 单击“编辑”页面上的“保存”,但页面的预算仍显示为 350,000.00 美元。 接下来的情况取决于并发冲突的处理方式:
-
跟踪用户已修改的属性,并仅更新数据库中相应的列。
在这种情况下,数据不会丢失。 两个用户更新了不同的属性。 下次有人浏览英语系时,将看到 Jane 和 John 两个人的更改。 这种更新方法可以减少导致数据丢失的冲突数。 这种方法具有一些缺点:
- 无法避免数据丢失,如果对同一属性进行竞争性更改的话。
- 通常不适用于 Web 应用。 它需要维持重要状态,以便跟踪所有提取值和新值。 维持大量状态可能影响应用性能。
- 可能会增加应用复杂性(与实体上的并发检测相比)。
-
让 John 的更改覆盖 Jane 的更改。
下次有人浏览英语系时,将看到 2013/9/1 和提取的值 350,000.00 美元。 这种方法称为“客户端优先”或“最后一个优先”方案 。 客户端的所有值优先于数据存储的值。 基架代码不处理并发,“客户端优先”方案会自动执行。
-
阻止在数据库中更新 John 的更改。 应用通常会:
- 显示错误消息。
- 显示数据的当前状态。
- 允许用户重新应用更改。
这称为“存储优先”方案。 数据存储值优先于客户端提交的值。 本教程中使用了“存储优先”方案。 此方法可确保用户在未收到警报时不会覆盖任何更改。
4.解决方案:重写SaveChanges
在 FrameworkDbContext 的DbContext类中重写SaveChanges方法
代码如下:
public override int SaveChanges() { var saved = 0; while (saved !=0) { try { base.SaveChanges(); saved++; } catch (DbUpdateConcurrencyException ex) { foreach (var entry in ex.Entries) { var proposedValues = entry.CurrentValues; var databaseValues = entry.GetDatabaseValues(); foreach (var property in proposedValues.Properties) { var proposedValue = proposedValues[property]; var databaseValue = databaseValues[property]; } // Refresh original values to bypass next concurrency check entry.OriginalValues.SetValues(databaseValues); } } } return saved; }
代码可以在github中查找。我的开源项目修改的类的源码,源码地址:https://github.com/TerraMours/TerraMours_Gpt_Api/blob/main/TerraMours/TerraMours/Framework/Infrastructure/EFCore/FrameworkDbContext.cs
参考资料:https://learn.microsoft.com/zh-cn/ef/core/saving/concurrency?tabs=fluent-api
阅读如遇样式问题,请前往个人博客浏览: https://www.raokun.top
拥抱ChatGPT:https://first.terramours.site