EF Core – 乐观并发

前言

之前写过 EF Core 悲观并发, 这篇主要讲一下乐观并发.

乐观并发的机制可以看这篇的结尾.

Why Need This?

如果你用 EF Core 做数据管理, 建议你每个 Entity 都配置乐观并发.

因为 EF 的机制是这样的. 先把资料读出来, 然后修改资料, 然后 SaveChanges.

这个 SaveChanges 并不会把所有的资料都放入 update 语句中, 它会依据之前拿出来的资料做 compare, 如果发现值不同才会生产语句. 

由于有了这个"依赖读的资料做逻辑判断" 所以就会有并发的问题了.

比如读出来的数据是 columnA = value1 

间中有人把 value1 换成 value2

SaveChange 的时候, 由于我们没有修改 columnA 所以 EF Core 不会生产语句 set columnA = value1

所以哪怕我是在另一个人之后做了 update, 但是数据库这时并不是我以为的 value1. (我们的逻辑告诉我们, entity save changes 之后, 数据库资料 should be 和我的 entity 一样阿)

为了解决这样的混乱, 做一个乐观并发就可以了

主要参考

Handling Concurrency Conflicts

Concurrency Tokens

ConcurrencyToken vs RowVersion

RowVersion vs ConcurrencyToken In EntityFramework/EFCore

Conflict detection in EF Core

RowVersion

在 Entity 加入 RowVersion Property

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public int Age { get; set; }
    public byte[] RowVersion { get; set; } = null!;
}

Setup Model

modelBuilder.Entity<Product>().Property(e => e.RowVersion).IsRowVersion();

执行

var product = db.Products.AsTracking().First();
product.Name = "New Product Name";
try
{
    db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
}

运行的语句多了一个 Where RowVersion = old row version value

如果间中有人对这个 row 做了修改 (不管是谁修改的, 哪怕是 SQL Trigger 改了这个 row, row version 都会自动 update)

那么这个 update 就会失败记入 catch.

题外话: SQL Server 的 RowVersion 使用 binary 维护的, 它不是时间概念, 也不是数据 hash, 它就是一个 running number. 只要 insert/update 语句执行它就会改变 (哪怕没有任何 value change).

IsConcurrencyToken

Entity

public int MyRowVersion { get; set; }

Model

modelBuilder.Entity<Product>().Property(e => e.MyRowVersion).IsConcurrencyToken();

执行

var product = db.Products.AsTracking().First();
product.Name = "New Product Name";
try
{
    product.MyRowVersion++; // 手动 update
    db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
}

语句

和 RowVersion 区别

整个机制是一样的, 唯一的区别是 ConcurrencyToken 需要自己手动维护, SQL Server 不会在 insert/update 的时候自动去更新这个 row version 的 column.

另外 EF Core 不鼓励我们用 ConcurrencyToken 哦

当 RowVersion 遇上 Trigger

上面我们有说, 如果数据库有 trigger 的话, 我们在 EF 就无法同时修改 2 个 Entity, 

比如我修改了 Entity A 和 Entity B, 然后 SaveChanges

当运行 update A table 的时候 trigger 触发了, 然后它去 update 了 B

导致 B 的 row version 被修改了

然后 EF 跑下一句 update Entity B 语句, 加上 RowVersion = old value 就直接失败了. 

解决方法有 2 个思路

1. 不要在一个 SaveChanges 里同时修改 2 个有 Trigger 关系的 Entity, 一个一个修改, 确保拿到最新的 RowVersion, 或者你的顺序要正确.

2. 使用 ConcurrencyToken, 自己决定什么时候需要 update row version, 比如 trigger 的时候不更新. 只有 EF Core SaveChanges 才更新. 

目前看 2 个方案都都不理想.

第一个就是要 take care of 这种同时 update 多个 entity 在一个 SaveChanges 里的情况, 简单说就完全不要这样子做, 坚持 1 个 1 个 update, 因为即使现在没有 trigger, 但是也可能后来加入 trigger.

那以后还是有报错的可能性. 总不能等报错才 fix, 也不能加 trigger 的时候全场找这种情况. 

第二个也很糟, 因为 trigger 不更新 row version 意味着, 在 EF 如果我们想利用乐观并发就要注意, 不可依赖 Trigger 会更新的 column, 比如冗余数据.

因为它没有 RowVersion 的保护, 要依赖就要使用 transaction. 这个写起来不算难, 但如果 miss 掉的话, 它不会报错. 这个挺危险. 一不小心数据就逻辑错了. 比报错还糟糕.

而且还得另外维护一套自己 update row version 的机制. 

追根究底就是 EF 和 Trigger 八字不合, 所以就会遇到很多风水的问题咯. 

目前我会选择第 2 个解决方案, 因为 2 个都不好,但是第一个影响的范围会比较大,出现的机率也比较高, 第二个出现的机率比较小, 只是要非常小心, 走一步看一步先吧.

原文地址:https://www.cnblogs.com/keatkeat/p/15556149.html