关于 TransactionScope 的一切






4.93/5 (178投票s)
在本文中,我将通过事务相关的理论和代码示例,解释在各种场景下如何使用 TransactionScope 及其各种选项来管理实际事务。
目录
- 1. 引言
- 2. 背景
- 3. 如何使用 TransactionScope
- 4. 事务
- 5. 业务事务
- 6. 数据库事务
- 7. 本地事务
- 8. 分布式事务
- 9. 分布式事务系统架构
- 10. 连接事务
- 11. 隐式事务
- 12. 事务属性
- 13. 事务隔离级别
- 14. TransactionScope 默认属性
- 15. 事务隔离级别的选择
- 16. 要求-1
- 17. 要求-2
- 18. 分布式事务性能
- 19. 要求-3
- 20. 要求-4
- 21. 要求-5
- 22. 兴趣点
1. 引言
TransactionScope
是 .NET Framework 中一个非常特殊且重要的类。支持代码块的事务是该类的主要职责。我们经常使用此类来管理代码中的本地事务和分布式事务。TransactionScope
的使用非常简单明了。它非常可靠且易于使用。因此,它在 .NET 开发人员中非常受欢迎。在本文中,我将通过代码示例解释事务相关的理论,并展示各种可以使用 TransactionScope
及其各种选项来管理实际事务的场景。
2. 背景
对于任何业务应用程序来说,事务管理都非常非常重要。每一个大型开发框架都提供了一个用于管理事务的组件。.NET Framework 是一个大型开发框架,它也提供了自己的事务管理组件。在 .NET Framework 2.0 发布之前,我们使用 SqlTransaction
来管理事务。从 2.0 版本开始,.NET Framework 引入了 TransactionScope
类。这个类位于 System.Transactions 程序集中。这个类提供了一个事务框架,借助该框架,任何 .NET 开发人员都可以编写事务性代码,而无需了解太多细节。因此,它在 .NET 开发人员中非常受欢迎,并且他们广泛使用它来管理事务。但故事还没有结束。我可以说故事才刚刚开始。
在现实世界中,您会遇到一些特殊情况、特殊问题,仅仅了解如何使用 TransactionScope
是不够的。要解决死锁、超时等事务性问题,您必须了解与事务直接/间接相关的每一个概念。别无选择。因此,事务及其相关组件的概念需要清晰。
3. 如何使用 TransactionScope
在 .NET 应用程序中使用 TransactionScope
非常非常简单。任何人都可以按照以下步骤进行操作:
- 将 System.Transactions 程序集添加到项目中。
- 使用
TransactionScope
类通过 `using` 语句创建一个事务范围/区域。 - 编写需要事务支持的代码。
- 执行
TransactionScope.Complete
方法来提交并完成事务。
确实,就是这么简单。但在实际项目中,仅了解这些知识是不够的。您需要更多的事务相关知识,否则您将无法处理事务相关的问题。所以,首先,我们应该清楚事务概念。
4. 事务
什么是事务?您可以从各种来源(如维基百科、其他网站、书籍、文章、博客)找到事务的定义。简而言之,我们可以说,一系列被视为一个整体的工作,要么全部完成,要么不完成。
示例:将钱从银行账户 A 转账到账户 B
一系列(实际上是两个)任务/进程
- 从账户 A 提取金额
- 将该金额存入账户 B
我们理解,从账户 A 到账户 B 的转账包含两个独立的过程。只有当这两个过程都单独成功时,转账才会被认为是准确和成功的。如果不是这样,假设过程 1 成功但过程 2 失败,那么钱将从账户 A 中扣除,但不会存入账户 B。如果发生这种情况,那将非常糟糕,没有人会接受。
5. 业务事务
业务事务是客户/供应商/股东以及其他参与商业活动的其他方之间的交互。在本文中,我将不介绍任何关于业务事务的内容。
6. 数据库事务
在软件开发中,当我们说事务时,我们默认会猜测是数据库事务。在数据库事务中,我们可以说,一系列数据操作语句(insert/update/delete)作为一个整体执行。所有语句要么成功执行,要么所有语句都失败,从而使数据库处于一致状态。数据库事务实际上以准确的方式表示数据库状态的更改。
7. 本地事务
一种事务,其中一系列数据操作语句在单个数据源/数据库上作为一个整体执行。它实际上是一个由数据库直接处理的单阶段事务。为了管理本地事务,System.Transactions
有一个轻量级事务管理器 (LTM)。它充当网关。由 System.Transactions
启动的所有事务都由该组件直接处理。如果它根据某些预定义规则发现事务性质是分布式的,它会回退到 MSDTC 分布式事务。
8. 分布式事务
一种与多个数据源协同工作的事务称为分布式事务。如果事务失败,受影响的数据源将被回滚。在 System.Transactions
中,MSDTC (Microsoft Distributed Transaction Coordinator) 管理分布式事务。它实现两阶段提交协议。分布式事务比本地事务慢得多。当事务对象意识到需要分布式事务时,它会自动将本地事务升级为分布式事务。开发人员在此处无能为力。
9. 分布式事务系统架构
我们知道在分布式事务中,有多个站点参与。每个站点都有两个组件
- 事务管理器
- 事务协调器
1. 事务管理器:维护一个日志,并在需要恢复时使用该日志。它通过启动和完成事务来控制整个事务,并管理事务的持久性和原子性。它还协调一个或多个资源上的事务。有两种类型的事务管理器。
- 本地事务管理器:仅协调单个资源上的事务。
- 全局事务管理器:协调多个资源上的事务。
2. 事务协调器:启动站点上发生的事务的执行。将子事务分发到适当的站点,以便它们可以在这些站点上执行。协调每个站点的每个事务。结果,事务被提交或回滚到所有站点。
10. 连接事务
直接与数据库连接(SqlConnection
)绑定的事务称为连接事务。SqlTransaction
(IDbTransaction
) 是连接事务的一个例子。在 .NET Framework 1.0/1.1 中,我们使用 SqlTransaction
。
string connString = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
using (var conn = new SqlConnection(connString))
{
conn.Open();
using (IDbTransaction tran = conn.BeginTransaction())
{
try
{
// transactional code...
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "INSERT INTO Data(Code) VALUES('A-100');";
cmd.Transaction = tran as SqlTransaction;
cmd.ExecuteNonQuery();
}
tran.Commit();
}
catch(Exception ex)
{
tran.Rollback();
throw;
}
}
}
11. 隐式事务
一种事务,它自动识别需要事务支持的代码块,而无需显式提及任何与事务相关的内容。隐式事务不只是绑定到数据库,任何支持事务的提供程序都可以使用。TransactionScope
实现隐式事务。如果您查看 TransactionScope 的用法,您不会发现任何事务相关的内容被发送到任何方法或设置任何属性。如果代码块属于任何 TransactionScope,则会自动将其与事务关联。WCF 事务是事务感知提供程序的另一个示例。任何人都可以编写像 WCF 实现一样的事务感知提供程序。
12. 事务属性
事务有四个重要属性。我们称之为 ACID 属性。它们是
- A-原子性
- C-一致性
- I-隔离性
- D-持久性
- 原子性:如果事务的所有部分单独成功,则数据将被提交,数据库将被更改。如果事务的任何一部分失败,那么事务的所有部分都将失败,数据库将保持不变。事务的一部分可能因各种原因而失败,例如业务规则违反、断电、系统崩溃、硬件故障等。
- 一致性:事务将遵循各种数据库规则(如各种数据完整性约束(主键/唯一键、检查/非空约束、具有有效引用的引用完整性、级联规则)等),将数据库从一个有效状态更改为另一个有效状态。
- 隔离性:一个事务将对另一个事务隐藏。换句话说,我们可以说,如果两个事务并发执行,那么一个事务将不会影响另一个事务。
- 持久性:在事务成功完成后(提交到数据库)后,更改的数据在任何情况下(如系统故障、数据库崩溃、硬件故障、断电等)都不会丢失。
13. 事务隔离级别
现在我将开始解释与事务直接相关的非常重要的一点,那就是事务隔离级别。为什么它如此重要?首先,我之前解释过隔离性是重要的事务属性。它描述了每个事务都与其他事务隔离,并且不会影响其他并发执行的事务。事务管理系统如何实现这一重要功能?
事务管理系统引入了锁定机制。借助此机制,一个事务可以与其他事务隔离。锁定策略的行为因每个事务设置的隔离级别而异。.NET 事务范围中有四种非常重要的隔离级别。它们是
- 串行化
- 可重复读
- 读已提交
- 读未提交
在开始解释隔离级别之前,我需要解释事务内部的数据读取机制。这种数据读取机制对于正确理解隔离级别非常重要。
- 脏读:一个事务读取了另一个事务已更改但尚未提交的数据。您可能会根据该数据做出决定/采取行动。当稍后回滚数据时,问题就会出现。如果发生回滚,您的决定/行动将是错误的,并会在您的应用程序中产生 bug。
- 不可重复读:一个事务多次读取同一表中的同一数据。当每次读取的数据都不同时,问题就会出现。
- 幻读:假设一个事务首先读取一个表,发现有 100 行。当同一个事务进行另一次读取时,发现有 101 行,问题就会出现。额外的行称为幻行。
现在我将简要解释重要的隔离级别
- 串行化:最高级别的隔离。在读写发生时,它会独占锁定数据。它获取范围锁,以防止创建幻行。
- 可重复读:第二高级别的隔离。与串行化相同,除了它不获取范围锁,因此可能会创建幻行。
- 读已提交:它允许共享锁并仅读取已提交的数据。这意味着永远不要读取事务中间的已更改数据。
- 读未提交:它是最低级别的隔离。它允许脏读。
现在我将开始解释 TransactionScope
及其用法模式
14. TransactionScope 默认属性
了解 TransactionScope
对象的默认属性非常重要。为什么?因为很多时候我们创建并使用该对象而不进行任何配置。
三个非常重要的属性是
IsolationLevel
Timeout
TransactionScopeOptions
我们像这样创建和使用 TransactionScope
using (var scope = new TransactionScope())
{
//transctional code…
scope.Complete();
}
这里 TransactionScope
对象是用默认构造函数创建的。我们没有为 IsolationLevel
、Timeout
和 TransactionScopeOptions
定义任何值。因此,它会获得这三个属性的默认值。所以现在我们需要知道这些属性的默认属性值是什么。
属性 | 默认值 | 可用选项 |
---|---|---|
IsolationLevel | 串行化 | Serializable, Read Committed, Read Un Committed, Repeatable Read |
Timeout | 1 分钟 | 最多 10 分钟 |
TransactionScopeOption | 必需 | Required, Required New, Suppress |
- 隔离级别:它定义了事务内部数据读取的锁定机制和策略。
- 超时:对象将等待事务完成多长时间。切勿将其与
SqlCommand
的Timeout
属性混淆。SqlCommand
Timeout
定义SqlCommand
对象等待数据库操作(select/insert/update/delete)完成多长时间。 - TransactionScopeOption:这是一个枚举。此枚举中有三个可用选项
否 | 选项 | 描述 |
---|---|---|
1 | 必需 |
这是 TransactionScope 的默认值。如果已存在任何事务,它将加入该事务,否则创建新事务。 |
2 |
RequiredNew | 选择此选项时,始终会创建一个新事务。此事务与其外部事务无关。 |
3 |
Suppress | 选择此选项时,不会创建任何事务。即使已经 |
如何知道这些属性的默认值?
System.Transactions 程序集有两个类
- 事务
- TransactionManager
这些类将提供默认值。在 TransactionScope
内部,如果您运行以下代码,您将知道默认值:
using (var scope = new System.Transactions.TransactionScope())
{
IsolationLevel isolationLevel = Transaction.Current.IsolationLevel;
TimeSpan defaultTimeout = TransactionManager.DefaultTimeout;
TimeSpan maximumTimeout = TransactionManager.MaximumTimeout;
}
是否可以覆盖默认属性值?
是的,您可以。假设您希望默认值为 30 秒,最大超时值为 20 分钟。如果这是要求,则可以使用 web.config 来实现。
<system.transactions>
<defaultSettings timeout="30"/>
<machineSettings maxTimeout="1200"/>
</system.transactions>
machineSettings
值,您需要更新服务器上的 machine.config 文件。<section name="machineSettings" type="System.Transactions.Configuration.MachineSettingsSection,
System.Transactions,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089,
Custom=null"allowdefinition="MachineOnly"allowexedefinition="MachineToApplication" />
15. 事务隔离级别的选择
在使用隔离级别时,您需要有正确的知识。下表将为您提供一个基本概念,以便您可以理解基础知识并为您的事务范围选择合适的隔离级别。
隔离级别 | 建议 |
---|---|
串行化 | 它在读/写操作时独占锁定数据。因此,很多时候它可能会导致死锁,从而导致超时异常。您可以将此隔离级别用于高度安全的事务性应用程序,例如金融应用程序。 |
可重复读 | 与串行化相同,除了允许幻行。可用于金融应用程序或重事务性应用程序,但需要知道幻行创建场景不存在的地方。 |
读已提交 | 大多数应用程序都可以使用它。SQL Server 的默认隔离级别是这个。 |
读未提交 | 具有这些的应用程序无需支持并发事务。 |
现在我将通过场景解释如何使用 TransactionScope
16. 要求-1
创建一个隔离级别为读已提交且事务超时为 5 分钟的事务。
实现
var option = new TransactionOptions();
option.IsolationLevel = IsolationLevel.ReadCommitted;
option.Timeout = TimeSpan.FromMinutes(5);
using (var scope = new TransactionScope(TransactionScopeOption.Required, option))
{
ExcuteSQL("CREATE TABLE MyNewTable(Id int);");
scope.Complete();
}
首先,创建 TransactionOptions
并将其 IsolationLevel
和 Timeout
属性分别设置为 ReadCommitted
和 5 分钟。
其次,通过创建具有参数化构造函数的 TransactionScope
对象来创建一个事务块。在此构造函数中,您将传递一个您之前创建的 TransactionOptions
对象以及 TransactionScopeOption.Required
值。
一个重要的注意事项:很多时候我们在事务中使用 DDL(数据定义语言)语句时会感到困惑,并且会提出一个问题:它是否支持 DDL 事务?答案是肯定的。您可以在事务中使用 DDL 语句,如 create/alter/drop 语句。您甚至可以在事务中使用 Truncate
语句。
17. 要求-2
我们需要创建一个事务,其中一个数据库操作将在我的本地数据库中,而另一个将在远程数据库中。
实现
using (var scope = new TransactionScope()) { UpdateLocalDatabase(); UpdateRemoteDatabase(); scope.Complete(); }
本地或远程/分布式事务的实现代码在事务中没有区别。之前我说过 TransactionScope
实现隐式事务类型。这意味着它会自动标记需要事务支持的代码块,无论是本地还是远程。但是,在处理分布式事务时,您可能会遇到错误。错误消息将如下所示:
"伙伴事务管理器已禁用对远程/网络事务的支持。"
如果您遇到这种类型的异常,您需要配置 MSDTC 的安全设置,包括本地和远程服务器,并确保服务正在运行。
要查找 MSDTC 配置界面,请转到
控制面板 > 管理工具 > 组件服务 > 分布式事务协调器 > LocalDTC
安全选项卡的一些选项描述如下:
属性名称 | 描述 |
---|---|
网络 DTC 访问 | 如果未选中,MSDTC 将不允许任何远程事务 |
允许远程客户端 | 如果选中此项,MSDTC 将允许为事务协调远程客户端。 |
允许远程管理 | 允许远程计算机访问和配置这些设置。 |
允许入站 | 允许计算机将事务流向本地计算机。当 MSDTC 作为资源管理器(如 SQL Server)托管时,需要此选项。 |
允许出站 | 允许计算机将事务流向远程计算机。这对于发起事务的客户端计算机是必需的。 |
相互身份验证 | 本地和远程计算机使用加密消息进行通信。它们使用 Windows 域帐户建立安全连接进行消息通信。 |
需要传入呼叫身份验证 | 如果无法建立相互身份验证,但传入的呼叫者已通过身份验证,则将允许通信。它仅支持 Windows 2003/XP ServicePack-2。 |
无需身份验证 | 它允许任何未经身份验证的非加密通信。 |
启用 XA 事务 | 允许不同的操作系统使用 XA 标准与 MSDTC 进行通信。 |
DTC 登录帐户 | DTC 服务运行帐户。默认帐户是 Network Service。 |
18. 分布式事务性能
分布式事务比本地事务慢。两阶段提交协议用于管理分布式事务。两阶段提交协议只不过是一种执行分布式事务的算法。主要使用三种提交协议
- 自动提交:如果所有 SQL 语句都成功执行,则事务会自动提交;如果其中任何一条语句执行失败,则会回滚。
- 两阶段提交:事务在最终提交前等待所有参与事务的其他方的消息。它在提交或回滚之前锁定资源。因此,它被称为阻塞协议。在性能方面,它是其速度慢得多的原因。它是管理分布式事务的常用协议。
- 三阶段提交:如果所有节点都同意,则事务最终提交。它是一种非阻塞协议。在性能方面,它比两阶段提交协议更快。该协议复杂且昂贵,但避免了两阶段提交协议的一些缺点。
19. 要求-3
我想在另一个事务中创建一个事务。
实现
string connectionString = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
var option = new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
Timeout = TimeSpan.FromSeconds(60)
};
using (var scopeOuter = new TransactionScope(TransactionScopeOption.Required, option))
{
using (var conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText="INSERT INTO Data(Code, FirstName)VALUES('A-100','Mr.A')";
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
}
using (var scopeInner = new TransactionScope(TransactionScopeOption.Required, option))
{
using (var conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText="INSERT INTO Data(Code, FirstName) VALUES('B-100','Mr.B')";
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
}
scopeInner.Complete();
}
scopeOuter.Complete();
}
在另一个(嵌套)事务中创建事务没有问题。您应该定义内部事务的行为。这种行为取决于 TransactionScopeOption
的值。如果您选择 Required
作为 TransactionScopeOption
,它将加入其外部事务。这意味着如果外部事务已提交,则内部事务将提交;如果外部事务已回滚,则内部事务将回滚。如果您选择 TrasnactionScopeOption
的 RequiredNew
值,则会创建一个新事务,并且此事务将独立提交或回滚。在处理 TransactionScope
的嵌套事务之前,您必须清楚这些概念。
20. 要求-4
我想从事务中显式调用回滚。
实现
using (var scope = new TransactionScope())
{
//either 1 of following lines will use
Transaction.Current.Rollback();
scope.Dispose();
//if you comment the following line transaction will
//automatically be rolled back
//scope.Complete();
}
如果您不调用 TransactionScope.Complete()
方法,则事务将自动回滚。如果您需要为某些场景显式调用回滚,则有两种选择:
- 执行
Transaction.Current.Rollback()
将回滚当前事务。 - 执行
TransactionScope.Dispose()
也将回滚当前事务。
只有一件事:请记住,如果您显式调用 Transaction.Rollback()
或 TranactionScope.Dispose()
,则不应调用 TransactionScope.Complete()
方法。如果您这样做,您将收到一个 ObjectDisposeException
。
"无法访问已释放的对象。对象名称:'TransactionScope'"
21. 要求-5
我想在事务范围内动态创建一个文件/文件夹。如果我的事务被回滚,我希望创建的文件/文件夹被自动删除,就像数据库行一样。
实现
string newDirectory = @"D:\TestDirectory";
string connectionString = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
using (var scope = new TransactionScope())
{
using (var conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "Insert into data(Code) values ('A001');";
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
}
Directory.CreateDirectory(newDirectory);
File.Create(@"D:\NewFile.txt").Dispose();
scope.Dispose();
}
TranactionScope
不仅限于数据库。它支持文件系统、MSMQ 等其他数据源。但您需要更多工作来支持 TranactionScope。首先,我在上面的代码块中显示的 **将不起作用**。为什么?因为默认情况下,目录创建和文件创建不会被标记为事务。那么我们需要做什么?
public interface IEnlistmentNotification
{
void Commit(Enlistment enlistment);
void InDoubt(Enlistment enlistment);
void Prepare(PreparingEnlistment preparingEnlistment);
void Rollback(Enlistment enlistment);
}
System.Transactions
命名空间有一个名为 IEnlistmentNotification
的接口。如果我希望我的组件/服务支持事务,我需要实现该接口。以下代码将展示一种非常简单直接的实现方式:
public class DirectoryCreator : IEnlistmentNotification
{
public string _directoryName;
private bool _isCommitSucceed = false;
public DirectoryCreator(string directoryName)
{
_directoryName = directoryName;
Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None);
}
public void Commit(Enlistment enlistment)
{
Directory.CreateDirectory(_directoryName);
_isCommitSucceed = true;
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
enlistment.Done();
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
preparingEnlistment.Prepared();
}
public void Rollback(Enlistment enlistment)
{
if (_isCommitSucceed))
Directory.Delete(_directoryName);
enlistment.Done();
}
}
上面的类将创建一个目录(文件夹),并且该组件支持事务。我们可以将此类与任何 TranactionScope 一起使用,如果 TranactionScope 已提交,则目录将被创建;否则,它将被删除(如果已创建)。我这里只展示了目录的创建,如果您愿意,可以创建一个用于文件创建的类/组件。那么,如何在事务范围中使用这个类呢?
string newDirectory = @"D:\TestDirectory";
string connectionString = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
using (var scope = new TransactionScope())
{
using (var conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "Insert into data(Code) values ('A001');";
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
}
var creator = new DirectoryCreator(newDirectory);
Transaction.Current.Rollback();
//scope.Complete();
}
现在,它将起作用!
Transactional NTFS(TxF) .NET 是一个开源项目。您可以使用此库在 transactionscope 中创建/写入/复制文件/目录,它将自动支持事务。
- 您首先需要从 http://txfnet.codeplex.com 下载组件。
- 将该组件作为引用添加到您的项目中。
- 在您的事务块中使用组件 API。
TxF API 用法代码示例
using (var ts = new System.Transactions.TransactionScope())
{
using (var conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "Insert into data(Code) values ('A001');";
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
}
TxF.Directory.CreateDirectory("d:\\xyz", true);
TxF.File.CreateFile("D:\\abc.txt", File.CreationDisposition.OpensFileOrCreate);
ts.Complete();
}
TxF 组件支持
- 创建/删除目录
- 创建/删除文件
- 读取/写入文件
- 复制文件
22. 兴趣点
事务管理实际上是一个庞大的主题。它也是一个非常复杂的主题,特别是分布式事务。我已尽力将其呈现得尽可能简单。如果您想获得所有事务相关的知识,您应该对此进行更多研究。我建议您阅读有关事务的研究论文,特别是分布式事务。
您还可以探索更多关于事务感知服务/组件的内容。我在这里展示了一种非常简单的实现方式。但在实际生活中,您可能会面临困难的场景。所以你需要为此做好准备。不久的将来,微软可能会添加事务感知组件,如字典/文件系统/目录服务等。如果发生这种情况,开发人员的生活将会更加轻松。
示例源代码
我已将源代码示例附加到本文中。我使用 Visual Studio 2012 和 .NET Framework 4.5 编写了此源代码。我添加了一个单元测试项目,以便您可以调试/测试代码并正确理解它。
参考文献
- http://msdn.microsoft.com/en-us/library/ms172152(v=vs.110).aspx
- http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope(v=vs.90).aspx
- http://en.wikipedia.org/wiki/ACID
- http://stackoverflow.com/questions/974596/what-is-a-transaction
- http://msdn.microsoft.com/en-us/library/system.transactions.transactionscopeoption(v=vs.110).aspx
- http://stackoverflow.com/questions/224689/transactions-in-net
- http://technet.microsoft.com/en-us/library/dd145385.aspx
- http://msdn.microsoft.com/en-us/magazine/cc163388.aspx