【问题标题】:Optimistic concurrency with Entity Framework and MySQLEntity Framework 和 MySQL 的乐观并发
【发布时间】:2011-11-28 08:19:25
【问题描述】:

我目前正在使用 Entity Framework 4.1 和 MySQL 开发应用程序。我想使用乐观并发,因此需要创建一个允许 EF 检测并发问题的表结构。我的目标与此类似:http://blogs.msdn.com/b/alexj/archive/2009/05/20/tip-19-how-to-use-optimistic-concurrency-in-the-entity-framework.aspx

我的问题是 MySQL 中的时间戳类型与 MS SQL Server 不同。除此之外,时间戳和日期时间都没有在 MySQL 中提供亚秒级精度(http://feedblog.org/2007/05/26/why-doesnt-mysql-support-millisecond-datetime-resolution/)。因此,这些类型在检测并发问题方面会非常糟糕。

我可以使用什么其他数据类型来解决这个问题?我在考虑也许使用Guid。但是这种方法有两个潜在的问题: 1. MySQL 将 Guid 存储为 char(36),这使得它们非常低效。 2. 我不确定 EF 是否需要严格增加行版本,或者它是否足够独特。

【问题讨论】:

  • 系统有多复杂?例如,您能否使用预提交挂钩来增加行上的整数(如手动实现的、特定于行的ROWVERSION)?
  • 所有插入和更新都通过相同的通用方法运行,因此我可以在那里增加行版本。但是,代码可能会在多台不同的机器上运行,因此很难在服务器之间进行同步。
  • 您当然不必同步它;如果您要防范内部工作单元的更改,它应该已经是自动的;如果您在调用之间(在客户端)持有 rowversion,那么只需与您获取的记录上的 rowversion 进行比较...?
  • 酷,我就是这么想的。我只是不确定如何在 EF 中实现并发检查。因此,唯一重要的是您可以在获取后检测更新,而此解决方案为您提供了这一点。即使上下文中更新的 rowversion 可能与数据库中同时更新的行的 rowversion 相同。我理解正确吗?

标签: c# .net mysql entity-framework concurrency


【解决方案1】:

大警告:未经测试 - 只是大声思考。

EF 支持覆盖SaveChanges,因此也许一种选择是定义一个接口,例如:

interface IVersionedRow {
    int RowVersion {get;set;}
}

并向模型类和数据库表添加int RowVersion 属性/字段,并使用partial class 实现此接口(使用隐式接口实现):

partial class Customer : IVersionedRow {}
partial class Order : IVersionedRow {}
...

然后覆盖SaveChanges,类似于:

public override int SaveChanges(SaveOptions options)
{    
    foreach (ObjectStateEntry entry in
        ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
    {
        var v = entry.Entity as IVersionedRow;
        if(v != null) v.RowVersion++;
    }
    return base.SaveChanges(options);
}

然后应该作为手动实现的行版本计数器起作用(理论上 - 未经测试)。为RowVersion 启用更改验证,这应该会起作用。

【讨论】:

  • 谢谢,看起来很棒!如果两个不同的上下文同时运行代码会发生什么?增量操作未在不同上下文中同步的任何潜在问题?
  • @Yrlec 对于行版本,您不希望它在上下文之间传播 - 您希望除第一个之外的所有人都编辑行以引发大错误消息.
  • @Yrlec 并回答问题;说rowversion当前是42;两个线程获取该记录并尝试保存为 43;但请记住,您为该列启用了值检查。所以 一个 会成功(并将其从 42 更改为 43),而另一个会失败(因为它要求连续 42,但找到了 43) .这是我们想要的,不是吗?
【解决方案2】:

这是经过测试的解决方案(适用于 EF6 及更高版本)。

您必须在模型中执行以下操作:

    [Table("some_table")]
    public class SomeEntity : IVersionedRow //define an interface as described previously and replace int type to long
    {
        ...
        [Column("row_version")]
        public long RowVersion { get; set; }
    }

然后在你的上下文中:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<SomeEntity>()
        .Property(p => p.RowVersion).IsConcurrencyToken();
    base.OnModelCreating(modelBuilder);
}

public override int SaveChanges()
{    
    var objectContextAdapter = this as IObjectContextAdapter;
    if (objectContextAdapter != null) {
        objectContextAdapter.ObjectContext.DetectChanges();
        foreach (ObjectStateEntry entry in objectContextAdapter.ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified)) {
            var v = entry.Entity as IVersionedRow;
            if (v != null) 
                v.RowVersion++;
        }
    }
    return base.SaveChanges();
}

现在,当您进行更新或删除操作时,不要忘记包含 DbUpdateConcurrencyException 。它非常适合我。

【讨论】:

    【解决方案3】:

    我刚刚提交了一个PR to MySQL .NET Connector v6.9.10,它为这个问题提供了一个解决方案,在 EF 和非 EF 应用程序之间提供了乐观锁定。详情请见https://stackoverflow.com/a/50147396/365261

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-02
      • 2016-03-02
      相关资源
      最近更新 更多