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

Posted

技术标签:

【中文标题】使用 EF Core 和 MySQL 实现行版本的更好方法?【英文标题】:Better way to implement a row version with EF Core and MySQL? 【发布时间】:2017-03-16 15:05:51 【问题描述】:

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

[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 增量,因此在数据库上只有一次更新(我假设触发器实际上每次都会进行这两次更新)?

【问题讨论】:

[Timestamp]public byte[] RowVersion 有效吗?它通常用于 MSSQL,但不知道 MySQL Provider 是否也可以处理它。与 MSSQL 不同,MySQL 没有针对此的行版本数据类型的类型 至于覆盖 SaveChanges ......不,它不会起作用。它仅在您想在保存之前更改值时才有用,但它必须保持不变并且仅在保存查询期间更改。 RowVersion 将用于 where 条件的更新查询:WHERE Id=? AND RowVersion=? 1 秒内 -- CURRENT_TIMESTAMP 的精度为微秒(6 位)。我不会太担心同步更新。 @GertArnold 我不太确定。数据库中显示的所有时间戳都精确到秒。更重要的是,当它们在 C# 中转换为 DateTime 然后通过 JSON 发送时,它们肯定是第二个解决方案。因此,为了在更新时再次检查它,任何短于几秒的东西似乎都会丢失。 @Tseng 我也尝试过 byte[] 类型,但无论如何,问题在于 [Timestamp] 属性 EF 期望 DB 是修改值的那个。如果我要摆脱触发器,我需要它发生在客户端。 【参考方案1】:

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

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

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

【讨论】:

在将 Pomelo.EntityFrameworkCore.MySql 驱动程序与 ASP.NET Core 应用程序一起使用时,我不得不使用此示例。虽然我更喜欢使用 Fluent API 进行这样的配置。与 MS 文档 heremodelBuilder.Entity() .Property(p => p.RowVersion) .HasDefaultValue(0) .IsConcurrencyToken();中的内容相比,我不得不对其进行一些修改> 在保存更改以跟踪实体的“修改”状态之前,您会调用什么?我似乎无法让它工作。我的版本实际上从未增加。

以上是关于使用 EF Core 和 MySQL 实现行版本的更好方法?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Devart.MySql 将 EF6 升级到 EF Core 3

ef core操作mysql

ASP - 在启动时核心迁移 EF Core SQL DB

MySQL 是不是与 EF Core 2.0 兼容?

EF Core MySQL 数据库优先?

MsSql Mysql Ef Core DBFirst 根据数据库更新实体类