【问题标题】:WCF Entity Framework ConcurrencyWCF 实体框架并发
【发布时间】:2010-12-06 12:53:49
【问题描述】:

我有一个 WCF 服务正在调用我的 Entity Framework Repository 类来访问数据。我正在使用 Entity Framework 4 CTP,并且正在使用我自己的 POCO 对象而不是自动生成的实体对象。

上下文生命周期仅限于方法调用。对于 Select/Insert 和 Update 方法,我创建上下文并在返回断开连接的实体对象的同一方法中处理它。

我现在正在尝试找出处理并发问题的最佳方法。例如,这就是我的更新方法的样子

public static Sale Update(Sale sale)
{
    using (var ctx = new DBContext())
    {
        var SaleToUpdate =
            (from t in ctx.Sales where t.ID == sale.ID select t).FirstOrDefault();
        if (SaleToUpdate == null) throw new EntityNotFoundException();
        ctx.Sales.ApplyCurrentValues(sale);
        ctx.SaveChanges();
        return sale;
    }
}

这很好用,但是因为我以断开连接的方式工作,所以如果在您拾取记录后已修改记录,则不会引发异常。这会导致并发问题。

当您在 WCF 上使用实体框架并且不保留全局上下文时,解决此问题的最佳方法是什么?

我能想到的唯一方法是给我的对象一个版本号,并在每次调用 save 时递增它。然后,这将允许我在保存之前检查版本是否更改。这不是我所知道的最简洁的解决方案,并且仍然允许客户更改他们的版本号,我真的不希望他们能够这样做。

编辑: 使用 Ladislav Mrnka 对我的实体中的 RowVersion 字段的建议,我的每个实体现在都有一个名为 RowVersion 类型的版本的字段。然后我将我的 Update 方法更改为如下所示。

public static Sale Update(Sale sale)
{
    using (var ctx = new DBContext())
    {
        var SaleToUpdate =
            (from t in ctx.Sales where t.ID == sale.ID select t).FirstOrDefault();
        if (SaleToUpdate == null) throw new EntityNotFoundException();
        if (!sale.Version.SequenceEqual(SaleToUpdate .Version)) 
            throw new OptimisticConcurrencyException("Record is out of date");
        ctx.Sales.ApplyCurrentValues(sale);
        ctx.SaveChanges();
        return sale;
    }
}

它似乎有效,但如果我应该以不同的方式做,请告诉我。我尝试通过将版本字段并发模式设置为固定来使用内置于并发控制的实体框架,不幸的是,当我执行查询以获取未更改的 SaleToUpdate 时,这不起作用,它选择了它的版本并使用它来进行并发检查这显然是当前的。感觉实体框架可能在这里遗漏了一些东西。

【问题讨论】:

  • 在 NHibernate 中,您可以将其配置为使用时间戳字段来了解数据是否已更改。我对 EF 感到非常沮丧,以至于我现在已经切换到 NHibernate - 尽管我从未尝试过 EF4.0 CTP 的东西。
  • 这对我来说似乎不对。您不应该读取这些值,然后将其保存。在您读取和保存之间,值可能会发生变化,尽管变化很小,但可能会。最好用你的值保存,如果 rowversion 发生变化则捕获异常。 EF 会为您检查,因为它的并发类型是固定的。或者将读取和保存更改封装在事务中。 EF 将在您保存时开始事务,但不会更早(读取)。
  • @Gavin:就像 MarcelDevG 正确提到的那样,您不需要(也不应该)手动检查行版本。只需使用我在答案中显示的 TimestampAttribute 就可以了。当然,如果您愿意,您可以尝试将 SaveChanges 包装起来并捕获 OptimisticConcurrencyException
  • 内置并发异常永远不会被抛出,因为我首先从数据库中获取对象,然后附加更改它保留了新检索到的数据库记录中的版本
  • 好的,我想我们在这里跑题了,但是您可以将您的 POCO 附加到上下文中,然后调用 ChangeObjectState 将状态更改为修改 然后 SaveChanges()。顺便说一句,这是一个重要的话题,值得提出自己的问题。

标签: wcf entity-framework concurrency


【解决方案1】:

如前所述,最佳实践是在数据库表中使用行版本类型的列进行并发检查,但如何使用 Code First 实现:
在 CTP3 中使用 Code First 时,您需要使用 fluent API 来描述哪些属性需要并发检查,但在 CTP4 中,这可以使用数据注释属性作为类定义的一部分以声明方式完成:

并发检查属性:

ConcurrencyCheckAttribute 用于指定某个属性在模型中具有“固定”的并发模式。固定并发模式意味着此属性是保存操作期间实体的并发检查的一部分,并且仅适用于 标量 属性:

public class Sale
{
    public int SaleId { get; set; }

    [ConcurrencyCheck]
    public string SalesPersonName { get; set; }    
}

在这里,ConcurrencyCheck 将为 SalesPersonName 属性打开。但是,如果您决定在您的类中包含一个 byte[] 类型的专用 Timestamp 属性,那么 TimestampAttribute 肯定是一个更好的选择:

时间戳属性:

TimestampAttribute 用于指定 byte[] 属性在模型中具有“固定”并发模式,应将其视为 时间戳列 在存储模型上(CLR 类型中的不可为空的字节 [])。此属性仅适用于 byte[] 类型的标量属性,并且实体上只能存在一个 TimestampAttribute。

public class Sale
{
    public int SaleId { get; set; }

    [Timestamp]
    public byte[] Timestamp { get; set; }
}

这里,不仅Timestamp属性将被视为并发令牌,而且EF Code First也知道该属性的存储类型为timestamp,并且这是一个计算列 而且我们不会在这个属性中插入值,而是会在 SQL Server 本身上计算该值。

【讨论】:

    【解决方案2】:

    不要使用自定义版本号。使用数据库的内置行版本数据类型。每次更改记录时都会自动修改行版本数据类型。例如 MSSQL 有 Timestamp 数据类型。您可以在 EF 中使用时间戳列并将其设置为固定并发处理程序(不确定如何使用 EF Code First 来执行此操作,但我相信 fluent API 具有这种可能性)。时间戳列必须作为字节数组(8 个字节)映射到 POCO 实体。当您调用更新方法时,您可以自己检查加载对象的时间戳和传入对象的时间戳,以避免对数据库的不必要调用。如果您不自己进行检查,它将在 EF 中通过在 update 语句中设置 where 条件来处理。

    【讨论】:

    • 谢谢,我不知道 RowVersion 数据类型。
    【解决方案3】:

    看看Saving Changes and Managing Concurrency

    来自文章:

    try
    {
        // Try to save changes, which may cause a conflict.
        int num = context.SaveChanges();
        Console.WriteLine("No conflicts. " +
        num.ToString() + " updates saved.");
    }
    catch (OptimisticConcurrencyException)
    {
        // Resolve the concurrency conflict by refreshing the 
        // object context before re-saving changes. 
        context.Refresh(RefreshMode.ClientWins, orders);
    
        // Save changes.
        context.SaveChanges();
        Console.WriteLine("OptimisticConcurrencyException "
        + "handled and changes saved");
    }
    

    【讨论】:

      猜你喜欢
      • 2011-09-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-10
      • 1970-01-01
      相关资源
      最近更新 更多