处理并发冲突Handling Concurrency Conflicts

乐观并发性涉及到乐观地尝试将实体保存到数据库,希望数据在加载实体后未发生更改。 如果事实证明数据已更改,则会引发异常,并且在尝试再次保存之前必须解决冲突。 本主题介绍如何在实体框架中处理此类异常。 本主题所介绍的方法同样适用于查询使用 Code First 和 EF 设计器创建的模型。

这篇文章并不适合完整讨论开放式并发。 以下各节介绍了并发解决方案的一些知识,并显示了常见任务的模式。

其中的许多模式使用属性值中讨论的主题。

在使用独立关联(其中外键未映射到实体中的属性)时解决并发性问题比使用外键关联更难。 因此,如果你要在应用程序中执行并发解析,则建议你始终将外键映射到你的实体中。 以下所有示例假设你使用的是外键关联。

当尝试保存使用外键关联的实体时,如果检测到乐观并发异常,则 SaveChanges 将引发 DbUpdateConcurrencyException。

通过重载解决开放式并发异常(数据库入选)Resolving optimistic concurrency exceptions with Reload (database wins)

可以使用 Reload.sql 方法,用数据库中的值覆盖当前实体的值。 然后,通常以某种形式向用户返回该实体,并且这些实体必须重试更改,然后重新保存。 例如:

  1. using (var context = new BloggingContext())
  2. {
  3. var blog = context.Blogs.Find(1);
  4. blog.Name = "The New ADO.NET Blog";
  5. bool saveFailed;
  6. do
  7. {
  8. saveFailed = false;
  9. try
  10. {
  11. context.SaveChanges();
  12. }
  13. catch (DbUpdateConcurrencyException ex)
  14. {
  15. saveFailed = true;
  16. // Update the values of the entity that failed to save from the store
  17. ex.Entries.Single().Reload();
  18. }
  19. } while (saveFailed);
  20. }

模拟并发异常的一种好方法是在 SaveChanges 调用上设置断点,然后使用其他工具(如 SQL Server Management Studio)修改要保存在数据库中的实体。 您还可以在 SaveChanges 之前插入一行,以便使用 SqlCommand 直接更新数据库。 例如:

  1. context.Database.SqlCommand(
  2. "UPDATE dbo.Blogs SET Name = 'Another Name' WHERE BlogId = 1");

DbUpdateConcurrencyException 上的条目方法返回未能更新的实体的 DbEntityEntry 实例。 (此属性当前总是返回并发问题的单个值。 对于常规更新异常,它可能会返回多个值。)在某些情况下,另一种方法可能是从数据库中获取可能需要重新加载的所有实体的条目,并为每个实体调用 “重新加载”。

将开放式并发异常解析为客户端入选Resolving optimistic concurrency exceptions as client wins

以上使用重载的示例有时被称为数据库入选或存储入选,因为该实体中的值由数据库中的值覆盖。 有时,您可能希望执行相反的操作,并用实体中当前的值覆盖数据库中的值。 这有时称为客户端入选,可以通过获取当前数据库值并将其设置为实体的原始值来完成。 (有关当前值和原始值的信息,请参阅使用属性值。)例如:

  1. using (var context = new BloggingContext())
  2. {
  3. var blog = context.Blogs.Find(1);
  4. blog.Name = "The New ADO.NET Blog";
  5. bool saveFailed;
  6. do
  7. {
  8. saveFailed = false;
  9. try
  10. {
  11. context.SaveChanges();
  12. }
  13. catch (DbUpdateConcurrencyException ex)
  14. {
  15. saveFailed = true;
  16. // Update original values from the database
  17. var entry = ex.Entries.Single();
  18. entry.OriginalValues.SetValues(entry.GetDatabaseValues());
  19. }
  20. } while (saveFailed);
  21. }

开放式并发异常的自定义解决Custom resolution of optimistic concurrency exceptions

有时,您可能想要将数据库中当前的值与实体中的当前值组合在一起。 这通常需要一些自定义逻辑或用户交互。 例如,您可能向用户提供窗体,其中包含当前值、数据库中的值和一组默认的已解析值。 然后,用户将根据需要编辑解析的值,并将这些已解决的值保存到数据库中。 为此,可以使用 DbPropertyValues 对象,该对象是从实体条目的 CurrentValues 和 GetDatabaseValues 返回的。 例如:

  1. using (var context = new BloggingContext())
  2. {
  3. var blog = context.Blogs.Find(1);
  4. blog.Name = "The New ADO.NET Blog";
  5. bool saveFailed;
  6. do
  7. {
  8. saveFailed = false;
  9. try
  10. {
  11. context.SaveChanges();
  12. }
  13. catch (DbUpdateConcurrencyException ex)
  14. {
  15. saveFailed = true;
  16. // Get the current entity values and the values in the database
  17. var entry = ex.Entries.Single();
  18. var currentValues = entry.CurrentValues;
  19. var databaseValues = entry.GetDatabaseValues();
  20. // Choose an initial set of resolved values. In this case we
  21. // make the default be the values currently in the database.
  22. var resolvedValues = databaseValues.Clone();
  23. // Have the user choose what the resolved values should be
  24. HaveUserResolveConcurrency(currentValues, databaseValues, resolvedValues);
  25. // Update the original values with the database values and
  26. // the current values with whatever the user choose.
  27. entry.OriginalValues.SetValues(databaseValues);
  28. entry.CurrentValues.SetValues(resolvedValues);
  29. }
  30. } while (saveFailed);
  31. }
  32. public void HaveUserResolveConcurrency(DbPropertyValues currentValues,
  33. DbPropertyValues databaseValues,
  34. DbPropertyValues resolvedValues)
  35. {
  36. // Show the current, database, and resolved values to the user and have
  37. // them edit the resolved values to get the correct resolution.
  38. }

使用对象的开放式并发异常的自定义解决Custom resolution of optimistic concurrency exceptions using objects

上面的代码使用 DbPropertyValues 实例来传递当前、数据库和解析的值。 有时,使用实体类型的实例可能会更容易。 可以使用 DbPropertyValues 的 ToObject 和 SetValues 方法完成此操作。 例如:

  1. using (var context = new BloggingContext())
  2. {
  3. var blog = context.Blogs.Find(1);
  4. blog.Name = "The New ADO.NET Blog";
  5. bool saveFailed;
  6. do
  7. {
  8. saveFailed = false;
  9. try
  10. {
  11. context.SaveChanges();
  12. }
  13. catch (DbUpdateConcurrencyException ex)
  14. {
  15. saveFailed = true;
  16. // Get the current entity values and the values in the database
  17. // as instances of the entity type
  18. var entry = ex.Entries.Single();
  19. var databaseValues = entry.GetDatabaseValues();
  20. var databaseValuesAsBlog = (Blog)databaseValues.ToObject();
  21. // Choose an initial set of resolved values. In this case we
  22. // make the default be the values currently in the database.
  23. var resolvedValuesAsBlog = (Blog)databaseValues.ToObject();
  24. // Have the user choose what the resolved values should be
  25. HaveUserResolveConcurrency((Blog)entry.Entity,
  26. databaseValuesAsBlog,
  27. resolvedValuesAsBlog);
  28. // Update the original values with the database values and
  29. // the current values with whatever the user choose.
  30. entry.OriginalValues.SetValues(databaseValues);
  31. entry.CurrentValues.SetValues(resolvedValuesAsBlog);
  32. }
  33. } while (saveFailed);
  34. }
  35. public void HaveUserResolveConcurrency(Blog entity,
  36. Blog databaseValues,
  37. Blog resolvedValues)
  38. {
  39. // Show the current, database, and resolved values to the user and have
  40. // them update the resolved values to get the correct resolution.
  41. }