65.9K
CodeProject 正在变化。 阅读更多。
Home

Entity Framework 6 中的并发处理(数据库优先方法)

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.93/5 (5投票s)

2017 年 5 月 1 日

CPOL

9分钟阅读

viewsIcon

36455

使用数据库优先方法在 Entity Framework 6 中处理数据并发。

引言

在本文中,我们将学习如何使用 Entity Framework 6 的数据库优先方法处理数据并发冲突。我们将创建一个具有 Entity Framework 数据库优先方法的控制台应用程序,并使用乐观方法处理数据并发冲突。

什么是并发冲突?

并发冲突是指两个或多个用户尝试更新同一条记录的情况,最后更新数据库的用户将覆盖其他用户的更改。如果某些更改被覆盖不会造成严重影响的应用程序,则无需处理并发冲突。但是,如果存在这样一种情况:在您即将更新该记录的时间段内,该记录已被修改,那么您的记录不应在未确认的情况下覆盖当前修改的记录。在这种情况下,我们需要处理并发冲突。

我们可以通过两种方式处理并发冲突。

1. 悲观并发

悲观并发处理并发冲突涉及使用数据库锁。如果用户想从数据库读取一行,用户会请求读锁定或写锁定。如果用户锁定一行以进行写访问,则其他用户将无法请求读锁定或写锁定。但是,如果用户请求读锁定,那么其他用户也可以对其进行读锁定,但不能进行写锁定。

但是,随着应用程序用户数量的增加,管理锁可能很复杂,并且可能导致性能问题。

2. 乐观并发

在乐观并发中,用户在读取行时不会请求锁定。当用户尝试更新该行时,系统必须确定自读取该行以来是否已被另一个用户修改。

总的来说,我们可以这样解释:在乐观并发控制中,在表中引入了一个时间戳类型的版本列。假设初始值为零(实际上它以字节形式存储值,而不是以数字形式)。现在,有两个用户 User1 和 User2 准备更新同一条记录。要更新记录,他们需要先获取记录。获取后,他们将获得具有**相同版本值零**的记录。现在 User1 已进行了更改并单击了保存按钮。在将数据存储到表中时,数据库将首先检查版本值是否相同。由于 User1 现在具有版本值零,这与之前获取的值零相同,因此它将允许保存数据。现在版本值将自动递增到 1,因为它属于**时间戳**类型(文章中稍后将解释时间戳是什么)。

现在 User2 也完成了他的更改并单击了保存按钮。数据库将再次检查版本值,但当 User2 获取值进行更新时,当时其版本值为零,但由于 User1 已经更新了记录,因此表中版本值已更改为 1。因此,版本将不匹配,数据库将不允许用户更新记录。我们将在下面的代码中通过示例进行说明。

乐观并发具有更好的性能,因为它不需要锁定,并且记录的锁定需要额外的服务器资源。

使用代码

我们将创建一个控制台应用程序来探索如何在 Entity Framework 数据库优先方法中处理并发冲突。涉及的步骤如下:

步骤

1. 使用 Visual Studio,创建一个控制台应用程序(文件 -> 新建 -> 项目 -> 控制台应用程序(来自 Visual C# 模板)),并将其命名为 ConcurrencyConflicthandelExample。

Choose Console Application

2. 在此控制台应用程序中安装 **EntityFramework** Nuget 包。为此,请在程序包管理器控制台中运行“Install-Packag EntityFramework”命令。或者,您也可以通过工具 -> Nuget 程序包管理器 -> 管理解决方案的 Nuget 程序包来安装。

install entity frame work

安装 Entity Framework 后,您可以展开**引用**并检查其中是否添加了 Entity Framework DLL。

3. 现在右键单击项目名称,然后单击添加 -> 新项。从那里选择数据 -> ADO.NET Entity Data Model。命名它为 EmployeeManagement。单击**添加**。

Choose DataModel

单击添加按钮后,将打开实体数据模型向导。由于我们在这里使用的是 Entity Framework 数据库优先方法,因此请选择**EF 设计器来自数据库。**然后单击**下一步**。

 Choose EF Desiggner

单击下一步后,将有一个选择数据连接的选项。在此处单击新建连接。将打开连接属性对话框。选择数据源。选择数据库服务器名称。选择登录类型。选择登录类型并提供必要的凭据后,连接到数据库选项将被启用。在选项框中,它将列出该服务器上的所有数据库。选择数据库名称。您可以通过单击测试连接选项来测试连接状态。现在单击**确定**。

Make Connection

单击确定按钮后,向导中的所有字段都将被填充。以黑色标记的字段是我们将在应用程序中使用的数据库。以红色标记的字段是将在配置文件中使用的完整连接字符串。以黄色标记的字段是负责与数据库进行交互的上下文类文件名。默认情况下,Entity Framework 会在选择的数据库名称后附加 Entity,这将是您的上下文文件名。但如果您愿意,也可以更改它。单击**下一步**。

Connection

单击下一步后,将出现**选择数据库对象和设置**选项。展开表选项并选择您要处理的所有表。单击**完成**。

Choose Table

单击完成,您的所有设置都已准备就绪。Entity Framework 将生成所有 tt 文件、上下文文件、edmx 文件。单击完成时,您将看到一个警告对话框弹出,提示“运行此文本模板可能会损害您的计算机。如果从不受信任的源获取,请不要运行它。”此警告仅仅是因为您的 tt 文件已在内部更改。只需单击确定即可。您将看到您的 edmx 文件已准备好所有关系。

以上就是所有代码设置。现在我们将执行一些操作,即我们本次讨论的主要议题,即并发冲突处理。

5. 在数据库中打开您要处理并发冲突的表。在我的例子中是 Department 表。

6. 添加一个名为 versionholder 的新列(您可以给出任何合适的名称)。选择其数据类型为 timestamp。**Timestamp** 暴露了一个由数据库自动生成、唯一的二进制数字,存储大小为 8 字节。

Update database Column

7. 现在打开您项目中的 .edmx 文件,在 Visual Studio 中。通过右键单击 .edmx 文件中的任意位置并选择**从数据库更新模型**选项来更新 Department 的表图。

 

Update .edmx file.

 

您会注意到在 department 模型中,我们刚刚在数据库中添加的 versionholder 列仍然不存在。

8. 单击更新模型后,将打开一个更新向导对话框。转到刷新选项,选择表,然后选择表名。

 

update table.

 

现在您将看到,我们已添加到数据库的列现在也已添加到我们的 .edmx 文件中。

 

Newly added column updated

注意*

如果在任何情况下您想重命名数据库中添加的 Versionholder 列或现有列,那么在更新 edmx 模型时,您可能会遇到模型中有两个属性的问题。第一个属性是旧的,第二个属性是更新后的名称,.cs 文件也将包含两个属性。在这种情况下,只需右键单击模型头部并选择“从模型中删除”选项来删除 edmx 模型。删除模型后,再次通过右键单击 .edmx 文件中的任意位置并选择“从数据库更新模型”来添加该模型。在此,从更新向导中,选择添加选项,然后从刚才删除的表选项中选择表名。

9. 现在右键单击新添加的属性,然后选择**属性**选项。

 

Add column property

10. 在“常规”属性下,将**并发模式**从“无”设置为**固定**。将并发模式设置为固定只会使版本值在每次更新时自动递增。

以上就是演示上面乐观并发定义中解释的示例的全部设置。现在我们将通过代码来了解它是如何工作的。

下面是处理并发冲突的完整代码结构。我将在下面解释代码。

namespace ConcurrencyConflictHandelExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Department User1 = null;
            Department User2 = null;

            //First User Brings The Data
            using (TestDBEntities context = new TestDBEntities())
            {
                User1 = context.Departments.Find(2);

            }
            //Second User Brings the same Data
            using (TestDBEntities context = new TestDBEntities())
            {
                User2 = context.Departments.Find(2);

            }
            //User1 updating the record
            using (TestDBEntities context = new TestDBEntities())
            {
                context.Departments.Attach(User1);
                User1.Credits += 1;
                context.SaveChanges();

            }
            //User1 updating the same record
            using (TestDBEntities context = new TestDBEntities())
            {
                context.Departments.Attach(User2);
                User2.Credits += 2;
                context.SaveChanges();

            }

            Console.Read();
        }
    }
}

首先,我们创建了两个用户来更新部门数据。

            Department User1 = null;
            Department User2 = null;

现在,使用上下文类,两个用户将获取相同的部门表记录。

           //First User Brings The Data
            using (TestDBEntities context = new TestDBEntities())
            {
                User1 = context.Departments.Find(2);

            }

            //Second User Brings the same Data
            using (TestDBEntities context = new TestDBEntities())
            {
                User2 = context.Departments.Find(2);

            }

这里两个用户将获得相同的部门数据,且版本值相同。假设这里两个用户获取的版本值都是零。

现在 User1 将更新部门记录。

            //User1 updating the record
            using (TestDBEntities context = new TestDBEntities())
            {
                context.Departments.Attach(User1);
                User1.Credits += 1;
                context.SaveChanges();

            }

这里发生的情况是,在保存时,Entity Framework 将首先将此更新记录的版本值与现有记录进行比较。由于此时尚未进行任何更新,因此版本值不会改变,并且比较结果为真,数据将被保存。您可以在下方查看 Entity Framework 执行此操作时生成的查询。

Sql profiler

现在 User2 将更新部门数据。

            //User2 updating the same record
            using (TestDBEntities context = new TestDBEntities())
            {
                context.Departments.Attach(User2);
                User2.Credits += 2;
                context.SaveChanges();

            }

由于 User2 仍然拥有版本值零,因此在进行数据操作并尝试保存数据时,版本值将不匹配,因为 User1 更新记录时版本值已被更改。我们将在 SQL Profiler 中查看 Entity Framework 生成的查询中的版本值。

 2nd user sql profiler

您可以看到 Credits 值不同,即 User2 设置的 43,但版本值与 User1 相同。但是实际的版本值已更改,因此不会匹配,并会抛出“DbUpdateConcurrencyException”。

Exception

我们可以使用此异常来警告第二个用户屏幕上的记录已被修改。这就是我们可以处理并发冲突的方式。

但是,如果要求在发生并发冲突时不在屏幕上显示警告,而是直接在代码中处理它呢?是的,我们也可以做到。让我们通过代码来看看。

这里的思路是获取当前数据库值,并将它们设置为实体的原始值。

            //User1 updating the same record
            using (TestDBEntities context = new TestDBEntities())
            {
                context.Departments.Attach(User2);
                User2.Credits +=2;
                try
                {
                    context.SaveChanges();
                }
                catch(DbUpdateConcurrencyException ex)
                {
                    var value = ex.Entries.Single();
                    value.OriginalValues.SetValues(value.GetDatabaseValues());
                    context.SaveChanges();
                   
                }

            }

现在它将保存值而不会抛出异常。

结论

在本文中,我们学习了如何配置 Entity Framework 数据库优先方法,并通过乐观方法处理并发冲突。

© . All rights reserved.