问题:用 EF Core 和 MySQL 实现行版本的更好方法?

如果我在模型中使用以下字段:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[Timestamp]
public DateTime RowVersion { get; set; }

然后将列定义为

`RowVersion` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

我从 EF 获得了正确的乐观并发行为。也就是说,我对为此使用时间戳并不感到兴奋,因为它似乎只是第二个分辨率。虽然 2 个客户端尝试在 1 秒内更新同一记录的可能性不大,但它肯定会发生,不是吗?

因此,考虑到这一点,我更喜欢一个简单的整数,它在每次更新时原子地递增 1。这样就不可能错过冲突。我将定义更改为:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[Timestamp]
public long RowVersion { get; set; }

问题是,MySQL 不会自动增加它。所以我创建了一个触发器:

CREATE TRIGGER update_row_version BEFORE UPDATE on client 
FOR EACH ROW
SET NEW.RowVersion = OLD.RowVersion + 1;

现在这一切都奏效了。 EF 会在需要时抛出 DbUpdateConcurrencyException,并且不会因为时间窗口而错过更新。但是,它使用触发器,我一直在阅读它们对性能的影响。

那么有没有更好的方法呢?也许某种方法可以覆盖 DbContext 的 SaveChanges() 以在客户端上执行 RowVersion 增量,因此在数据库上只有一次更新(我假设触发器实际上每次都会进行这两次更新)?

解答

好的,我想出了一个似乎不需要触发器的策略。

我添加了一个简单的界面:

interface ISavingChanges
{
    void OnSavingChanges();
}

模型现在看起来像这样:

public class Client : ISavingChanges
{
    // other fields omitted for clarity...


    [ConcurrencyCheck]
    public long RowVersion { get; set; }

    public void OnSavingChanges()
    {
        RowVersion++;
    }
}

然后我像这样覆盖 SaveChanges:

    public override int SaveChanges()
    {
        foreach (var entity in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified))
        {
            var saveEntity = entity.Entity as ISavingChanges;
            saveEntity.OnSavingChanges();
        }

        return base.SaveChanges();
    }

这一切都按预期工作。 ConcurrencyCheck 属性是让 EF 在 UPDATE SQL 的 SET 和 WHERE 子句中包含 RowVersion 字段的关键。

Logo

华为、百度、京东云现已入驻,来创建你的专属开发者社区吧!

更多推荐