用EF Core和MySQL实现行版本的更好方法



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

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

,然后将列定义为

`RowVersion` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

我从EF中得到适当的乐观并发行为。也就是说,我对使用时间戳并不感到兴奋,因为它似乎只是第二分辨率。虽然没有一个大的机会有两个客户端尝试在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增量,因此只对DB进行一次更新(我假设触发器实际上每次都进行两次更新)?

好吧,我想出了一个似乎很有效的策略,不需要触发。

我添加了一个简单的接口:

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字段的关键。

最新更新