Entity Framework 5.0如何处理乐观并发异常?

18

在处理context.SaveChanges()期间的多个潜在异常时,其中一个异常是OptimisticConcurrency。微软在http://msdn.microsoft.com/en-us/library/bb399228.aspx中对EF 4.x进行了讨论...

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");
}

......但是在EF 5.0(RC)中,这似乎不起作用,因为Refresh()在我的EF5,基于代码优先的DbContext类派生的context类上不存在。

我看到context.Entry(context.SalesOrderHeaders).Reload(); - 但这似乎只是直接从数据库重新加载,并不是刷新/合并(使用客户端胜利策略)。

有什么方法来处理EF5中的乐观并发异常吗? 实际上,关于SaveChanges()中异常处理的一般指针也很好。

谢谢

2个回答

31
在DbContext API中解决并发异常的方法是重新加载原始实体:
catch (DbUpdateConcurrencyException ex)
{
    // Get failed entry
    var entry = ex.Entries.Single(...);
    // Overwrite original values with values from database but don't
    // touch current values where changes are held
    entry.OriginalValues.SetValues(entry.GetDatabaseValues());
}

你还应该能够使用上述代码,但必须从你的DbContext实例中获取ObjectContext实例(它只是ObjectContext的一个包装器)。
catch (DbUpdateConcurrencyException ex)
{
    var objContext = ((IObjectContextAdapter)context).ObjectContext;
    // Get failed entry
    var entry = ex.Entries.Single(...);
    // Now call refresh on ObjectContext
    objContext.Refresh(RefreshMode.ClientWins, entry.Entity);        
}

你甚至可以尝试以下方法:
objContext.Refresh(RefreshMode.ClientWins, ex.Entries.Select(e => e.Entity));

2
在尝试理解您的DbContext API示例中的Single(...)时,我想通过catch (DbUpdateConcurrencyException ex){foreach (var entry in ex.Entries){entry.OriginalValues.SetValues(entry.GetDatabaseValues());}迭代所有失败来实现。您认为呢?此外,“不要触摸保存更改的当前值”对我来说不是很清楚:(..似乎两者都会获取dB值? - DeepSpace101
1
每个条目都包含当前值和原始值 - 服务器胜利策略会覆盖两个集合,但客户端胜利只会覆盖原始集合。 - Ladislav Mrnka
1
@DeepSpace101 有点晚了,但是EF 6.x的文档明确指出,在处理DbUpdateConcurrencyException时,Entries始终只包含1个实体(请参阅“使用Reload解决乐观并发异常”的最后一段)。 - Søren Boisen
额外信息:要刷新的对象集合中索引为0的元素处于添加状态。 处于此状态的对象无法刷新,此问题是插入后出现的并发异常。 - haitham sha

1
如果您的更改仅针对一个实体(特定的一行,而不是其他表等),那么可以通过处理旧实体并创建新实体来刷新上下文,并覆盖并发机制。 问题在于当上下文被处理时,每个已更改但尚未提交的实体都会与上下文分离,并且更改将丢失。因此,请注意您的工作单元的范围!
    catch (DbUpdateConcurrencyException)
    {
        context.Dispose();
        context = new DBContext();
        Entity entity = context.Set<Entity>().Find(entityFromOldContext.Id);

        entity.Property1 = entityFromOldContext.Property1;
        entity.Property2 += 4;

        context.commit();
    }

在实体中,我使用额外的属性来控制并发,如下所示:
[Timestamp]
public Byte[] RowVersion { get; set; }

也许这不是一种优雅的方式(并且违反了UnitOfWork模式),但在某些情况下可能会很有用,并最终成为上述帖子的替代方案。

你能详细说明它是如何破坏UnitOfWork模式的吗?我很感兴趣。 - julealgon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接