ADO.NET Entity Framework 中的事务隔离
本文介绍了一个示例,说明如何在 ADO.NET Entity Framework 中控制事务隔离级别。
引言
本文介绍了一个示例,说明如何在 ADO.NET Entity Framework 中控制事务隔离级别。
背景
在过去的二十年中,IT 行业见证了两项重大进步:“面向对象编程”和“实体关系模型”。“ADO.NET Entity Framework”以及其他“对象关系映射”工具,例如“Hibernate”和“NHibernate”,代表了连接这两个精彩的主流理论和实践成就的努力。
尽管“OOP”和“ERM”在看待世界的方式上存在根本差异,“ORM”工具取得了相当大的成功,这主要归功于自动代码生成功能。如果您喜欢自动代码生成功能,“ORM”工具可能适合您。但您需要准备好应对使用它们时遇到的一些问题。本文将通过一个简单的示例来解决我们在使用“ADO.NET Entity Framework”时遇到的一个问题。这个问题是如何控制事务隔离级别。

所附的 Visual Studio 解决方案在 Visual Studio 2010 中开发。“EFTransactionExample
”是一个简单的 WPF “MVVM”应用程序。“EmployeeModel.edmx”文件是基于 SQL Server 数据库由“Entity Framework”生成的“实体模型”。“MainWindow.xaml”文件是此 WPF 应用程序的主 UI 窗口。“Model
”和“View Model
”类位于其对应的文件夹中。“Utilities.cs
”文件实现了一些实用类,以帮助 WPF 应用程序中的“MVVM”绑定。
我将首先介绍本示例中将使用的 SQL Server 数据库。在我编写此示例时,我在本地计算机上运行的 SQL Server 2008 中创建了数据库。本文不是教您如何使用“ADO.NET Entity Framework”。它假定您对 Entity Framework、事务隔离和多线程有一些基本知识。如果您不熟悉这些主题,可以轻松找到参考资料。在此示例中,我使用 Windows 身份验证从 WPF 应用程序连接到数据库。如果您想运行示例,则需要确保您的计算机登录名具有执行数据库操作的足够权限。
SQL Server 数据库
为使示例保持简单,我创建了一个单表数据库。

如果您想运行所附示例,可以使用以下 SQL 脚本在您自己的环境中创建此数据库
USE [master]
GO
IF EXISTS (SELECT name FROM sys.databases
WHERE name = N'EmployeeSalary')
BEGIN
ALTER DATABASE [EmployeeSalary] SET SINGLE_USER WITH NO_WAIT
DROP DATABASE [EmployeeSalary]
END
GO
CREATE DATABASE [EmployeeSalary]
GO
USE [EmployeeSalary]
GO
CREATE TABLE [dbo].[Employee](
[ID] [int] NOT NULL,
[Name] [varchar](200) NOT NULL,
[Salary] [int] NOT NULL,
CONSTRAINT [PK_Employee] PRIMARY KEY CLUSTERED
([ID] ASC )
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
INSERT INTO [Employee] ([ID], [Name], [Salary])
VALUES (1, 'Barack Hussein Obama II', 100)
成功运行脚本后,您应该可以在服务器上创建一个名为 [EmployeeSalary]
的数据库。您还应该在此数据库中有一个名为 [Employee]
的表。[Employee]
表应该有一行数据,如下所示

这是我们继续进行示例所需的全部数据库内容。它是一个超“简单”的数据库,但它“复杂”到足以让我们研究“事务隔离”。现在,让我们假设有两个用户同时访问此数据库。
- CPO - 首席政治官
- CEO - 首席经济官
假设两位官员都对员工“Barack Hussein Obama II”的工作感到满意。他们都想将他的薪水提高 50 美元。每位用户将首先读取员工当前的薪水,并在此基础上增加 50 美元,然后将结果保存回数据库。在两位官员完成工作后,员工的正确薪水应该是 200 美元。但是,每位用户的数据库操作不是自动“原子”的。每位用户将经历三个步骤来给员工涨薪。
- 从数据库读取当前薪水。
- 在当前薪水的基础上增加 50 美元。
- 将结果保存回数据库。
如果 CPO 先读取了当前薪水,但在他对数据库进行更改之前,CEO 也读取了薪水,那么两位官员都将获得当前薪水值 100 美元。如果他们都从 100 美元的基础上增加 50 美元并将其保存回数据库,当他们都完成后,数据库中显示的员工最终薪水将是 150 美元。这显然不是期望的结果。为了让“Barack Hussein Obama II”获得他应得的正确涨薪,我们需要妥善地“隔离”两位官员的操作。有几个预定义的“事务隔离级别”。在这种情况下,我们需要将隔离级别设置为“Serializable”。
现代“数据库管理系统”已经建立了非常高效和成熟的事务隔离机制。由于“ADO.NET Entity Framework”是关系数据库之上的一个面向对象的抽象层,因此 Entity Framework 中的事务隔离不如我们直接在 SQL Server 上操作时那样高效。但它仍然得到支持。在我们讨论如何在 Entity Framework 中控制事务隔离之前,我将向您展示如何从项目中的 [EmployeeSalary]
数据库创建“实体模型”。
实体模型“EmployeeModel.edmx”
项目中的“EmployeeModel.edmx”文件是通过“ADO.NET Entity Data Model”模板生成的。
您可以在解决方案资源管理器中右键单击项目文件“EFTransactionExample
”以调出“添加新项”窗口,然后搜索“Entity
”以找到此模板。然后,您可以按照模板中的分步说明创建数据模型“EmployeeModel.edmx”。如果您不熟悉 Entity Framework,这个教程是一个不错的选择。添加数据模型完成后,Entity Framework 会在“EmployeeModel.edmx”文件的代码隐藏文件中创建两个类。
- “
EmployeeSalaryEntities
”类是“ObjectContext”类的子类,它提供了作为对象查询和处理实体数据的设施。 - “
Employee
”类是“EntityObject”类的子类。它代表[Employee]
表中的一个员工。
我们将使用“EmployeeSalaryEntities
”类来访问数据库。这个类提供了几个重载的构造函数。如果我们通过其默认构造函数初始化这个类,那么数据库访问将使用 Entity Data Model 模板在创建数据模型时保存在“App.Config”文件中的连接字符串。在本示例中,我们将在连接字符串中使用 Windows 身份验证。
应用程序的模型类
应用程序的模型类构建在 Entity Data Model 模板创建的实体模型之上,实现在“Models”文件夹中的“ApplicationModel.cs”文件中。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects;
using System.Transactions;
namespace EFTransactionExample.Models
{
class ApplicationModel
{
// Get all the information for the employees
public IEnumerable<Employee> GetEmployeeInformation()
{
EmployeeSalaryEntities context = new EmployeeSalaryEntities();
return context.Employees.AsEnumerable<Employee>();
}
// Set an employee's salary to a value
public void SetEmployeeSalary(int employeeID,
string employeeName, int salary)
{
EmployeeSalaryEntities context = new EmployeeSalaryEntities();
var employee = context.Employees.Where(e => e.ID == employeeID)
.SingleOrDefault<Employee>();
if (employee == null)
{
employee = new Employee()
{
ID = employeeID,
Name = employeeName,
Salary = salary
};
context.Employees.AddObject(employee);
}
employee.Salary = salary;
context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
}
public void RaiseEmployeeSalaryNonSerialized(int employeeID, int amount)
{
// Read employee information
EmployeeSalaryEntities context = new EmployeeSalaryEntities();
var employee = context.Employees.Where(e => e.ID == employeeID)
.Single<Employee>();
// sleep a while
System.Threading.Thread.Sleep(5000);
// Update the salary
employee.Salary = employee.Salary + amount;
context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
}
public void RaiseEmployeeSalarySerialized(int employeeID, int amount)
{
TransactionOptions options = new TransactionOptions();
options.IsolationLevel = IsolationLevel.Serializable;
using (TransactionScope scope
= new TransactionScope(TransactionScopeOption.Required, options))
using (EmployeeSalaryEntities context = new EmployeeSalaryEntities())
{
// Open the database connection explicitly
context.Connection.Open();
// Read employee information
var employee = context.Employees.Where(e => e.ID == employeeID)
.Single<Employee>();
// sleep a while
System.Threading.Thread.Sleep(5000);
// Update the salary
employee.Salary = employee.Salary + amount;
context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
scope.Complete();
}
}
}
}
“ApplicationModel
”类有四个方法
- “
GetEmpl
oyeeInformation”方法检索数据库中员工的列表。在本示例中,我们只有一个员工“Barack Hussein Obama II”。 - “
SetEmployeeSalary
”方法将员工的薪水设置为给定值。如果员工不存在,它将创建具有作为参数传入的“employeeID
”和“employeeName
”的员工。 - “
RaiseEmployeeSalaryNonSerialized
”方法将提高员工的薪水。此方法未显式指定事务隔离级别。 - “
RaiseEmployeeSalarySerialized
”方法也用于提高员工的薪水。但我们显式将事务隔离级别指定为“Serializable”。为了让数据库识别出对员工起始薪水的读取和对员工薪水的更新属于同一事务,我们需要显式打开“EmployeeSalaryEntities
”对象的连接。当“EmployeeSalaryEntities
”超出范围时,连接将关闭。
为了向您展示事务隔离在并发操作中的重要性,我将让“CPO”和“CEO”同时读取起始薪水 100 美元。我在代码中创建了一些人工延迟,让线程休眠 5 秒钟。在后面的代码中,您将看到“Serializable”事务隔离级别如何确保当两位官员都完成时,员工的薪水是 200 美元。
MVVM 模式的绑定实用程序
为了帮助构建 MVVM 应用程序,我在“BindingUtilities”文件夹中的“Utilities.cs”文件中创建了两个实用类。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.ComponentModel;
using System.Windows.Input;
namespace EFTransactionExample.BindingUtilities
{
public abstract class ViewModelBase
: DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class RelayCommand : ICommand
{
private readonly Action handler;
private bool isEnabled;
public RelayCommand(Action handler)
{
this.handler = handler;
}
public bool IsEnabled
{
get { return isEnabled; }
set
{
if (value != isEnabled)
{
isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
handler();
}
}
}
- “
ViewModelBase
”类将是此应用程序的 View Model 类的基类。它实现了“INotifyPropertyChanged”接口,因此我们可以简单地使用“NotifyPropertyChanged
”方法在绑定“Property”更改时通知应用程序的 XAML 视图。 - “
RelayCommand
”将用于此 MVVM 应用程序中的命令绑定。
我有一篇专门的文章“Data and Command Bindings for Silverlight MVVM Applications”来讨论 MVVM 绑定,如果您有兴趣,可以看一下。
ViewModel
此应用程序的视图模型在“ViewModels”文件夹中的“MainWindowViewModel.cs”文件中创建。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EFTransactionExample.BindingUtilities;
using EFTransactionExample.Models;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace EFTransactionExample.ViewModels
{
class MainWindowViewModel : ViewModelBase
{
// Thread safe locker
static readonly object statuslocker = new object();
static readonly object employeeInformationlocker
= new object();
// Private field
int EmployeeID = 1;
string EmployeeName = "Barack Hussein Obama II";
int EmployeeOriginalSalary = 100;
// Properties
private IEnumerable<Employee> employeeInformation;
public IEnumerable<Employee> EmployeeInformation
{
get { return employeeInformation; }
private set
{
employeeInformation = value;
NotifyPropertyChanged("EmployeeInformation");
}
}
private void UpdateEmployeeInformation()
{
lock (employeeInformationlocker)
{
ApplicationModel model = new ApplicationModel();
try
{
EmployeeInformation = model.GetEmployeeInformation();
}
catch (Exception ex)
{
string msg = "Unable to obtain employee information | "
+ ex.Message;
UpdateStatusInformation(msg);
}
}
}
private string statusInformation;
public string StatusInformation
{
get { return statusInformation; }
private set
{
statusInformation = value;
NotifyPropertyChanged("StatusInformation");
}
}
private void UpdateStatusInformation(string msg)
{
lock (statuslocker)
{
StatusInformation = msg;
}
}
private void InitiateViewModel()
{
UpdateEmployeeInformation();
}
// Commands
public RelayCommand ResetSalaryCommand { get; private set; }
private void ResetSalary()
{
ApplicationModel model = new ApplicationModel();
model.SetEmployeeSalary(EmployeeID, EmployeeName, EmployeeOriginalSalary);
EmployeeInformation = model.GetEmployeeInformation();
UpdateStatusInformation("Reset employee salary completed ..");
}
public RelayCommand RaiseSalaryNonSerializedCommand { get; private set; }
private void RaiseSalaryNonSerialized()
{
System.Threading.Thread
CPOThread = new System.Threading.Thread(
new System.Threading.ParameterizedThreadStart(WorkerNonSerialized));
CPOThread.Name = "CPO Thread";
System.Threading.Thread
CEOThread = new System.Threading.Thread(
new System.Threading.ParameterizedThreadStart(WorkerNonSerialized));
CEOThread.Name = "CEO Thread";
CPOThread.Start(new object[3] { 1, 50, 0 });
CEOThread.Start(new object[3] { 1, 50, 2500 });
}
private void WorkerNonSerialized(object parameters)
{
object[] parameterArray
= (object[])parameters;
int employeeID = (int)parameterArray[0];
int amount = (int)parameterArray[1];
int sleepBeforeStart = (int)parameterArray[2];
Thread.Sleep(sleepBeforeStart);
UpdateStatusInformation(Thread.CurrentThread.Name + " Started ..");
try
{
ApplicationModel model = new ApplicationModel();
model.RaiseEmployeeSalaryNonSerialized(employeeID, amount);
EmployeeInformation = model.GetEmployeeInformation();
}
catch (Exception ex)
{
string msg = "Unable to raise the salary for the employee | "
+ ex.Message;
UpdateStatusInformation(msg);
return;
}
UpdateEmployeeInformation();
UpdateStatusInformation(Thread.CurrentThread.Name + " Finished ..");
}
public RelayCommand RaiseSalarySerializedCommand { get; private set; }
private void RaiseSalarySerialized()
{
System.Threading.Thread
CPOThread = new System.Threading.Thread(
new System.Threading.ParameterizedThreadStart(WorkerSerialized));
CPOThread.Name = "CPO Thread";
System.Threading.Thread
CEOThread = new System.Threading.Thread(
new System.Threading.ParameterizedThreadStart(WorkerSerialized));
CEOThread.Name = "CEO Thread";
CPOThread.Start(new object[3] { 1, 50, 0 });
CEOThread.Start(new object[3] { 1, 50, 2500 });
}
private void WorkerSerialized(object parameters)
{
object[] parameterArray
= (object[])parameters;
int employeeID = (int)parameterArray[0];
int amount = (int)parameterArray[1];
int sleepBeforeStart = (int)parameterArray[2];
Thread.Sleep(sleepBeforeStart);
UpdateStatusInformation(Thread.CurrentThread.Name + " Started ..");
int NoOfTries = 0;
ApplicationModel model = new ApplicationModel();
try
{
model.RaiseEmployeeSalarySerialized(employeeID, amount);
}
catch (Exception ex)
{
NoOfTries++;
if (NoOfTries < 3)
{
string msg = Thread.CurrentThread.Name
+ " Failed to update the salary for the employ, re-trying ...";
UpdateStatusInformation(msg);
}
else
{
string msg = "Unable to raise the salary for the employee" +
", we have tried 3 times | " + ex.Message;
UpdateStatusInformation(msg);
return;
}
model.RaiseEmployeeSalarySerialized(employeeID, amount);
}
UpdateEmployeeInformation();
UpdateStatusInformation(Thread.CurrentThread.Name + " Finished ..");
}
private void WireCommands()
{
ResetSalaryCommand = new RelayCommand(ResetSalary);
ResetSalaryCommand.IsEnabled = true;
RaiseSalaryNonSerializedCommand
= new RelayCommand(RaiseSalaryNonSerialized);
RaiseSalaryNonSerializedCommand.IsEnabled = true;
RaiseSalarySerializedCommand
= new RelayCommand(RaiseSalarySerialized);
RaiseSalarySerializedCommand.IsEnabled = true;
}
public MainWindowViewModel()
{
InitiateViewModel();
WireCommands();
}
}
}
构建在“ApplicationModel
”类和绑定实用类之上,这个 View Model 类实现了两个 public
属性
- “
EmployeeInformation
”属性将用于显示数据库中员工的信息。 - “
StatusInformation
”属性将用于显示“CPO”和“CEO”为员工加薪时的信息。
它还实现了三个命令。
- “
ResetSalaryCommand
”将员工“Barack Hussein Obama II”的薪水重置为 100 美元。 - “
RaiseSalaryNonSerializedCommand
”启动两个线程。一个线程代表“CPO”,另一个线程代表“CEO”。每个线程调用“RaiseEmployeeSalaryNonSerialized
”方法为员工加薪。“CPO”线程立即启动,但“CEO”线程将在启动数据库操作前休眠 2.5 秒。请记住,“RaiseEmployeeSalaryNonSerialized
”方法在读取和更新操作之间有 5 秒的延迟。两个线程都将获得 100 美元作为起始薪水。由于我们没有强制执行“Serializable”事务隔离,当它们都完成后,员工的最终薪水将是 150 美元。 - “
RaiseSalarySerializedCommand
”与“RaiseSalaryNonSerializedCommand
”非常相似,不同之处在于强制执行了“Serializable”数据库操作。当“CPO”和“CEO”都读取起始薪水并都尝试对其进行更改时,两者都需要获取一个“Exclusive Lock”。由于他们在读取时都已获得员工的“Shared Lock”,因此我们遇到了“死锁”。SQL Server 将选择一个受害者来解决“死锁”,以便其他线程可以继续更新数据库。由于我们知道其中一个线程将成为受害者,所以我们让它再次尝试加薪。当它再次尝试时,另一个线程应该已经完成了加薪,起始薪水将是 150 美元,当它完成后,员工将获得应得的最终薪水 200 美元。
应用程序的 XAML 视图
此 MVVM 应用程序的 XAML 视图在“MainWindow.xaml”文件中实现。
<Window x:Class="EFTransactionExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontFamily="Calibri"
Title="MainWindow" Height="350" Width="525">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBlock Text="Entity Framework Transaction Isolation Level Experiment"
Margin="10, 0, 0, 10"
FontSize="16" FontWeight="SemiBold" Foreground="Brown" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="Re-set Salary"
Margin="10,0,5,0"
Command="{Binding ResetSalaryCommand}" />
<Button Grid.Column="1" Content="Raise Salary"
Margin="5,0,5,0"
Command="{Binding RaisetSalaryNonSerializedCommand}" />
<Button Grid.Column="2" Content="Raise Salary Serialized"
Command="{Binding RaisetSalarySerializedCommand}"
Margin="5,0,10,0"/>
</Grid>
<Border Grid.Row="2" BorderThickness="1" BorderBrush="Blue"
CornerRadius="5" Margin="5">
<DataGrid ItemsSource="{Binding EmployeeInformation}"
IsReadOnly="True" AutoGenerateColumns="False" Margin="10">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="50"
Binding="{Binding ID}" />
<DataGridTextColumn Header="Name" Width="300"
Binding="{Binding Name}" />
<DataGridTextColumn Header="Salary" Binding="{Binding Salary}" />
</DataGrid.Columns>
</DataGrid>
</Border>
<TextBlock Grid.Row="3" Text="{Binding StatusInformation}"
Margin="10, 0, 0, 0" />
</Grid>
</Window>
这个简单的 XAML 视图具有以下功能组件
- 一个“DataGrid”,绑定到 View Model 中的“
EmployeeInformation
”属性,用于显示员工信息。 - 一个“TextBlock”,绑定到 View Model 中的“
StatusInformation
”,用于显示“CPO”和“CEO”为员工加薪时的信息。 - 三个“Button”控件。每个控件都绑定到 View Model 中的相应命令。
运行应用程序
现在我们完成了这个简单的示例,让我们进行测试运行。当应用程序启动时,我们可以看到员工的起始薪水是 100 美元。

点击“Raise Salary”按钮,我们可以看到“CPO”和“CEO”都开始为员工加薪。当他们完成后,员工的最终薪水是 150 美元。

点击“Re-set Salary”按钮将员工的薪水重置为 100 美元,然后点击“Raise Salary Serialized”按钮,我们将看到其中一位官员第一次尝试会失败,但在他完成后,员工的最终薪水是 200 美元,这是员工应得的正确涨薪。

结论
在结束本文时,我将介绍一种实现相同结果的非常著名的方法。如果我们不使用“ADO.NET Entity Framework”,而是直接使用“存储过程”在关系数据库上操作。保证正确事务隔离级别的存储过程如下
CREATE PROCEDURE dbo.RaiseEmployeeSalary
@EmployID INT,
@Amount INT
AS
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
UPDATE Employee SET Salary = Salary + @Amount WHERE ID = @EmployID
END
它就像它看起来一样简单。在大多数情况下,简洁性也意味着更高的质量、更少的测试和“单元测试”,以及更容易的维护。它还意味着软件工程师可以更专注于他们被分配的任务,而不必担心代码是如何编写的。如果您回过头来看实现相同结果的“RaiseEmployeeSalarySerialized
”方法,您应该会发现存储过程确实更简单。“RaiseEmployeeSalarySerialized
”方法使用了许多技术和编程技巧,例如“TransactionScope”、“Using Statement”、“IDisposable”和“Linq”,但存储过程只有一个“T-SQL”语句。您还应该知道,要加薪,客户端需要与数据库进行两次往返(如果您使用 Entity Framework 进行读取和更新)。如果您使用存储过程,则只需要一次与数据库的往返。在性能方面,使用 Entity Framework 不如使用存储过程高效。另一个问题是,当您利用 Entity Framework 时,您需要授予您的应用程序在表级别访问数据的权限。我知道很多数据库管理员可能不喜欢这一点。
您可能会认为我不喜欢“ADO.NET Entity Framework”。不,我喜欢所有技术,它们都有优点和缺点。由我们来为我们的应用程序选择最适合的。“ADO.NET Entity Framework”确实带来了不错的自动代码生成功能。如果您是多层架构的粉丝,我们可以利用自动生成的代码来构建我们的数据访问层,从而节省一些打字工作。但您应该知道,在某些情况下,与根本不使用它相比,您可能被迫编写更多的代码来使用自动生成的代码。我不得不说,Entity Framework 并不排除我们使用存储过程。但是,您必须确切地知道 Entity Framework 是如何调用存储过程的。否则,您可能无法始终获得您期望的结果。
“面向对象编程”和“实体关系模型”是人类在理论和实践方面的两项了不起的成就。因为它们在看待世界的方式上存在一些根本性差异。连接它们不仅仅是一个简单的技术问题。人类可能需要一些根本性的理论改进来找到内在联系。我希望我们能尽快实现这一点,如果可能的话。
关注点
- 本文介绍了一个示例,说明如何在 ADO.NET Entity Framework 中控制事务隔离级别。
- 如果您刚进入 IT 领域,又刚开始接触数据库,您可能会担心因为设置了较高的事务隔离级别而导致用户无法访问您的数据库。事实上,让用户失败的成本远低于让他们成功但产生错误结果的成本。如果能向用户解释为什么坚持事务隔离,他们会乐于接受失败并重试。
- 在大多数数据库更新中,如果您发现事务隔离级别确实阻止了一些用户,正确的决定不是放宽隔离级别。事实上,您可能需要考虑提高隔离级别,因为这表明您的用户正在访问一些共享数据,并且您需要找到正确的事务隔离来保护它。要解决这个问题,您应该考虑重新设计您的业务流程,以便用户可以在不被其他用户打断的情况下处理数据。无论如何,修复一个充满错误数据的混乱数据库是一项非常困难的任务。
- 关系数据库已经足够成熟,可以解决大多数事务性应用程序的业务问题。随着时间的推移,我们积累了许多有效技术/方法来解决这些问题。如果“ORP”工具阻止您使用它们并给您带来不必要的困难。考虑不使用“ORP”工具并非坏事。如果“ORM”工具确实提供了某些优势,那么就去使用它们。您应该能够找到方法来解决您面前的问题。
- 我写这篇文章的时间很短,我意识到这篇文章可能存在一些问题。代码可能不符合良好的编码标准,异常处理可能无法捕获所有异常等等。我希望您可以忽略这些问题。总之,在 Entity Framework 中控制事务隔离级别是可能的,这是一个运行示例。
- 您可能想知道 Entity Framework 的默认事务隔离级别是什么。经过一些研究,我发现 Entity Framework 本身并不提供事务隔离控制。它依赖于数据源来控制它。当我们使用 SQL Server 时,如果我们不指定它,事务隔离级别是“Read Committed”。
- 在大多数应用程序中,您不太可能遇到此处呈现的并发问题。事实上,我们需要恰当地安排两个线程中官员的操作顺序来创建这个问题。但我们永远不能排除两位官员同时执行数据库操作的可能性。始终使用事务隔离妥善保护您的数据是一个好主意。
- 我希望您喜欢我的文章,希望本文能以某种方式帮助您。
历史
- 首次修订 - 2011 年 5 月 15 日