文章内容

2017/2/5 16:39:26,作 者: 黄兵

Entity Framework 乐观并发模式

Optimistic concurrency involves optimistically attempting to save your entity to the database in the hope that the data there has not changed since the entity was loaded. If it turns out that the data has changed then an exception is thrown and you must resolve the conflict before attempting to save again. This topic covers how to handle such exceptions in Entity Framework. The techniques shown in this topic apply equally to models created with Code First and the EF Designer.

注释:乐观并发涉及乐观地尝试将您的实体保存到数据库,希望自实体加载以来数据没有改变。 如果事实证明数据已更改,则会抛出异常,您必须解决冲突,然后再尝试重新保存。 本主题介绍如何在Entity Framework中处理此类异常。 本主题中显示的技术同样适用于使用Code First和EF Designer创建的模型。

This post is not the appropriate place for a full discussion of optimistic concurrency. The sections below assume some knowledge of concurrency resolution and show patterns for common tasks.

Many of these patterns make use of the topics discussed in Working with Property Values.

注释:这个帖子并不是讨论乐观并发的原理。 下面的部分假设一些并发解决的知识和显示普通任务的模式。

处理并发模式的使用以及属性,请移步到这里:Working with Property Values

Resolving concurrency issues when you are using independent associations (where the foreign key is not mapped to a property in your entity) is much more difficult than when you are using foreign key associations. Therefore if you are going to do concurrency resolution in your application it is advised that you always map foreign keys into your entities. All the examples below assume that you are using foreign key associations.

注释:当您使用独立关联(其中外键未映射到实体中的属性)时解决并发问题比使用外键关联要困难得多。 因此,如果您要在应用程序中执行并发性解决,建议您始终将外键映射到实体中。 下面的所有示例假设您使用外键关联。

A DbUpdateConcurrencyException is thrown by SaveChanges when an optimistic concurrency exception is detected while attempting to save an entity that uses foreign key associations.

注释:在尝试保存使用外键关联的实体时检测到乐观并发异常时,SaveChanges抛出DbUpdateConcurrencyException。

注释:使用Reload(数据库 wins)解析乐观并发异常

The Reload method can be used to overwrite the current values of the entity with the values now in the database. The entity is then typically given back to the user in some form and they must try to make their changes again and re-save. For example:

注释:重载方法可以用于用数据库中现在的值覆盖实体的当前值。 然后,通常以某种形式将实体给予用户,并且他们必须再次尝试进行改变并重新保存。 例如:

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

A good way to simulate a concurrency exception is to set a breakpoint on the SaveChanges call and then modify an entity that is being saved in the database using another tool such as SQL Management Studio. You could also insert a line before SaveChanges to update the database directly using SqlCommand. For example:

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

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

The Entries method on DbUpdateConcurrencyException returns the DbEntityEntry instances for the entities that failed to update. (This property currently always returns a single value for concurrency issues. It may return multiple values for general update exceptions.) An alternative for some situations might be to get entries for all entities that may need to be reloaded from the database and call reload for each of these.

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

注释:客户端优先方式

The example above that uses Reload is sometimes called database wins or store wins because the values in the entity are overwritten by values from the database. Sometimes you may wish to do the opposite and overwrite the values in the database with the values currently in the entity. This is sometimes called client wins and can be done by getting the current database values and setting them as the original values for the entity. (See Working with Property Values for information on current and original values.) For example:

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

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); 
}
注释:综合方式处理乐观并发

Sometimes you may want to combine the values currently in the database with the values currently in the entity. This usually requires some custom logic or user interaction. For example, you might present a form to the user containing the current values, the values in the database, and a default set of resolved values. The user would then edit the resolved values as necessary and it would be these resolved values that get saved to the database. This can be done using the DbPropertyValues objects returned from CurrentValues and GetDatabaseValues on the entity’s entry. For example:

注释:有时,您可能希望将当前数据库中的值与实体中当前的值组合。 这通常需要一些自定义逻辑或用户交互。 例如,您可以向用户提供一个包含当前值,数据库中的值和一组默认解析值的表单。 然后,用户将根据需要编辑解析的值,这些解析的值将保存到数据库。 这可以使用从实体条目上的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. 
}
注释:使用对象的乐观并发异常的自定义解析

The code above uses DbPropertyValues instances for passing around current, database, and resolved values. Sometimes it may be easier to use instances of your entity type for this. This can be done using the ToObject and SetValues methods of DbPropertyValues. For example:

注释:上面的代码使用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. 
}

更多内容点击这里:EntityFramwork--处理数据并发

分享到:

发表评论

评论列表