您的位置:首页 > C#综合知识 > Entity Framework >

c# Entity Framework 乐观并发模式 冲突处理

2014-08-11 18:36 来源:未知编辑:admin点击:

乐观并发指乐观地尝试将实体保存到数据库,以期数据自实体加载后未曾更改。但若数据确实发生了更改,则将引发异常,您必须先解决冲突,然后再尝试重新保存。本主题讲述如何在实体框架中处理此类异常。本主题中所示方法同样适用于使用 Code First 和 EF 设计器创建的模型。

本文并不全面讨论乐观并发。以下几节假定读者基本了解常见任务的并发解决方法和显示模式。

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

与使用外键关联相比,使用独立关联(其中外键未映射到实体中的属性)时解决并发问题要困难得多。因此,如果您要在应用程序中解决并发问题,最好始终将外键映射到实体。以下所有示例均假定您使用外键关联。

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

 

使用 Reload 解决乐观并发异常(数据库优先)

通过 Reload 方法,可使用数据库中的当前值覆盖实体的当前值。随后,通常以某种形式将实体归还给用户,他们必须尝试再次进行更改并重新保存。例如:

using (var context = new BloggingContext()) 
{ 
    var blog = context.Blogs.Find(1); 
    blog.Name = "The New ADO.NET Blog"; 
 
    bool saveFailed; 
    do 
    { 
        saveFailed = false; 
 
        try 
        { 
            context.SaveChanges(); 
        } 
        catch (DbUpdateConcurrencyException ex) 
        { 
            saveFailed = true; 
 
            // Update the values of the entity that failed to save from the store 
            ex.Entries.Single().Reload(); 
        } 
 
    } while (saveFailed); 
}

模拟并发异常的一个好方法是:对 SaveChanges 调用设置断点,然后使用 SQL Management Studio 等工具修改要保存在数据库中的实体。也可以在 SaveChanges 前面插入一行以直接使用 SqlCommand 更新数据库。例如:

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

DbUpdateConcurrencyException 的 Entries 方法为无法更新的实体返回 DbEntityEntry 实例。(对于并发问题,此属性当前始终返回单个值。对于一般更新异常,它可能会返回多个值。)一些情况下的替代方式是:为需要从数据库重新加载的所有实体获取条目,然后对其中每个实体调用 reload。

 

以“数据库优先”方式解决乐观并发异常

上面使用 Reload 的示例有时称为“数据库优先”或“存储优先”,因为实体中的值将被数据库中的值覆盖。有时,您可能希望执行相反的操作,即用当前位于实体中的值覆盖数据库中的值。这有时被称为“客户端优先”,可通过获取当前数据库值并将其设置为实体的原始值来实现。(有关当前值和原始值的信息,请参阅 使用属性值。)例如:

using (var context = new BloggingContext()) 
{ 
    var blog = context.Blogs.Find(1); 
    blog.Name = "The New ADO.NET Blog"; 
 
    bool saveFailed; 
    do 
    { 
        saveFailed = false; 
        try 
        { 
            context.SaveChanges(); 
        } 
        catch (DbUpdateConcurrencyException ex) 
        { 
            saveFailed = true; 
 
            // Update original values from the database 
            var entry = ex.Entries.Single(); 
            entry.OriginalValues.SetValues(entry.GetDatabaseValues()); 
        } 
 
    } while (saveFailed); 
}

 

自定义解决乐观并发异常

有时,您可能希望将当前位于数据库中的值与当前位于实体中的值合并起来。这通常需要一些自定义逻辑或用户交互。例如,您可能会向用户提供一个表单,其中包含当前值、数据库中的值以及一组默认的解析值。随后,用户将根据需要编辑解析值,这些解析值将保存到数据库中。可使用从实体条目的 CurrentValues 和 GetDatabaseValues 返回的 DbPropertyValues 对象来实现这一点。例如:

using (var context = new BloggingContext()) 
{ 
    var blog = context.Blogs.Find(1); 
    blog.Name = "The New ADO.NET Blog"; 
 
    bool saveFailed; 
    do 
    { 
        saveFailed = false; 
        try 
        { 
            context.SaveChanges(); 
        } 
        catch (DbUpdateConcurrencyException ex) 
        { 
            saveFailed = true; 
 
            // Get the current entity values and the values in the database 
            var entry = ex.Entries.Single(); 
            var currentValues = entry.CurrentValues; 
            var databaseValues = entry.GetDatabaseValues(); 
 
            // Choose an initial set of resolved values. In this case we 
            // make the default be the values currently in the database. 
            var resolvedValues = databaseValues.Clone(); 
 
            // Have the user choose what the resolved values should be 
            HaveUserResolveConcurrency(currentValues, databaseValues, resolvedValues); 
 
            // Update the original values with the database values and 
            // the current values with whatever the user choose. 
            entry.OriginalValues.SetValues(databaseValues); 
            entry.CurrentValues.SetValues(resolvedValues); 
        } 
    } while (saveFailed); 
} 
 
public void HaveUserResolveConcurrency(DbPropertyValues currentValues, 
                                       DbPropertyValues databaseValues, 
                                       DbPropertyValues resolvedValues) 
{ 
    // Show the current, database, and resolved values to the user and have 
    // them edit the resolved values to get the correct resolution. 
}

 

使用对象自定义解决乐观并发异常

上述代码使用 DbPropertyValues 实例传递当前值、数据库值和解析值。有时,使用实体类型的实例执行此操作可能更简单些。可使用 DbPropertyValues 的 ToObject 和 SetValues 方法实现这一点。例如:

using (var context = new BloggingContext()) 
{ 
    var blog = context.Blogs.Find(1); 
    blog.Name = "The New ADO.NET Blog"; 
 
    bool saveFailed; 
    do 
    { 
        saveFailed = false; 
        try 
        { 
            context.SaveChanges(); 
        } 
        catch (DbUpdateConcurrencyException ex) 
        { 
            saveFailed = true; 
 
            // Get the current entity values and the values in the database 
            // as instances of the entity type 
            var entry = ex.Entries.Single(); 
            var databaseValues = entry.GetDatabaseValues(); 
            var databaseValuesAsBlog = (Blog)databaseValues.ToObject(); 
 
            // Choose an initial set of resolved values. In this case we 
            // make the default be the values currently in the database. 
            var resolvedValuesAsBlog = (Blog)databaseValues.ToObject(); 
 
            // Have the user choose what the resolved values should be 
            HaveUserResolveConcurrency((Blog)entry.Entity, 
                                       databaseValuesAsBlog, 
                                       resolvedValuesAsBlog); 
 
            // Update the original values with the database values and 
            // the current values with whatever the user choose. 
            entry.OriginalValues.SetValues(databaseValues); 
            entry.CurrentValues.SetValues(resolvedValuesAsBlog); 
        } 
 
    } while (saveFailed); 
} 
 
public void HaveUserResolveConcurrency(Blog entity, 
                                       Blog databaseValues, 
                                       Blog resolvedValues) 
{ 
    // Show the current, database, and resolved values to the user and have 
    // them update the resolved values to get the correct resolution. 
}