【问题标题】:Entity Framework Many-to-Many Self Relationship and Optimistic Concurrency Control实体框架多对多自关系和乐观并发控制
【发布时间】:2015-04-22 16:11:44
【问题描述】:

我有一个具有多对多自我关系的实体。以这个实体为例:

public class User
{
    public int ID { get; set; }
    public string UserName { get; set; }

    public virtual ICollection<User> Friends { get; set; }
}

这是我配置映射的方式:

HasMany(t => t.Friends).WithMany()
    .Map(m => { 
        m.MapLeftKey("UserID");
        m.MapRightKey("FriendID");
        m.ToTable("UserFriends");
        });

由于这种关系现在由 EF 管理,因此我无法在我的代码中访问 UserFriends DbSet 并且无法处理对它的并发访问。 为了让这个组合能够处理并发访问(添加/删除),我是否需要自己处理多对多关系,然后添加一个 [Timestamp] 列,或者有没有办法告诉 EF 自己同时处理这个?就像模型构建器中的配置一样。

编辑:我正在使用 EF 6,目前如果实体上存在并发操作(例如,尝试删除当前未在数据库中退出的朋友)我收到以下错误消息和 DbUpdateException

保存不公开外键的实体时出错 他们关系的属性。 EntityEntries 属性将 返回 null,因为无法将单个实体标识为源 的例外。可以进行保存时的异常处理 通过在实体类型中公开外键属性更容易。看 InnerException 了解详情。

【问题讨论】:

  • 您能否详细说明您拥有什么样的并发访问以及如何发生冲突?例如。您的应用程序内是否只有多个线程在添加/删除,或者您的应用程序外部是否有进程也在更改数据?
  • 嗯,基本上是不同的客户端同时更改这些实体。他们可以同时为系统中注册的用户之一添加/删除好友。假设客户端 A 和客户端 B 从用户中删除了同一个朋友。这将导致异常。但是,目前我只得到一个DBUpdateException 而不是一个可以正确处理的OptimisticConcurrencyException
  • 您收到DBUpdateException 的消息了吗?另外,为了清楚起见,您使用的是什么版本的 EF?
  • @Asad 请查看我对您要求的信息的编辑。
  • 不幸的是,我认为你想要的东西是不可能的。请参阅efreversepoco.codeplex.com/workitem/53,它仍然开放。我认为您必须自己明确管理映射并添加时间戳字段。

标签: c# entity-framework optimistic-concurrency


【解决方案1】:

乐观并发不适用于此处。

联结表永远不会更新。它的记录要么被添加,要么被删除。这意味着没有需要行版本的 CRUD 操作。

所以其实并发是相当容易的:

  • 两个并发用户不能添加相同的关联,因为最后一个用户会遇到唯一键冲突。
  • 两个并发用户不能删除同一个关联,因为最后一个用户会看到异常数量的记录 (0) 受到影响。
  • 至于外键问题(添加/删除与同时删除的实体的关联)。这些也会引发异常。

因此,它归结为处理异常并将其转化为易于理解的用户反馈。在更新(和乐观并发)确实发挥作用的情况下,也必须处理所有这些情况。

【讨论】:

  • 第二种场景基本就是乐观并发,所以这里还是适用的。如果您加载的记录自您上次与数据库通信以来已被删除,则它们应该引发并发错误。
  • @Asad 尽管在乐观并发控制中,行版本(或任何并发令牌)也在删除语句中进行比较,但这仍然不适用于这里,因为连接记录永远不会更新。永远不会出现因为修改了并发令牌而拒绝删除的情况。
  • 我认为你的论点是基于一个错误的前提。前提是并发不适用于联结表。正如 Asad 所提到的,您的第二种情况基本上本身就是一个并发问题。那么怎么可能不适用呢?此外,DbUpdateExceptionDbUpdateConcurrencyException 是两个不同的东西。仅仅因为有一个更新异常,我不能假设它是因为并发问题并以这种方式处理它。在第二种情况下,EF 应该抛出一个“DbUpdateConcurrencyException”,它将在 e 参数中提供正确的信息。
  • @GertArnold 实际上第一个场景也是并发问题。并不是用户“不能”添加/删除这些记录……他们可以。问题是处理这些应该是“DbUpdateConcurrencyException”类型的异常,但它们不是。
  • 无论如何,我认为在这里争论语义并不太有用。您的回答基本上是正确的,但 OP 真正需要的是能够判断更新何时因并发问题而失败并进行处理。
【解决方案2】:

虽然 UserFriends 表上没有 rowversion 列,但 EF 仍然能够识别出 DbContext.SaveChanges 失败的原因是多对多关系的并发问题。在这种情况下,ef 会抛出 OptimisticConcurrencyException 异常,而 DbUpdateException 则不透明。使用以下代码捕获它:

try
{
    context.SaveChanges();
}
catch (DbUpdateException ex)
{
    if(ex.InnerException is OptimisticConcurrencyException)
    {
        // If you are here then there was concurrency problem in many-to-many relationship
    }
}

你可以不配置当前流利的api自动包含在UserFriends表的rowversion列中,但是在生成迁移之后你可以手动添加到UserFriends CreateTable语句定义rowversion列:

CreateTable(
    "dbo.UserFriends",
    c => new{
        RowVersion = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
        // other columns definitions
    });

但是,当多对多关系中的并发问题发生时,这不会改变 DbContext 的行为。

【讨论】:

    猜你喜欢
    • 2011-02-23
    • 2023-03-03
    • 1970-01-01
    • 2017-01-28
    • 2011-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多