TransactionScope 类提供了一种简单的方法,用来在参与事务时标记代码块。事务范围可以自动选择并管理环境事务。如果使用 new 语句实例化 TransactionScope,则事务管理器会决定要参与的事务。如果在事务范围(也就是在初始化 TransactionScope 对象和调用其 Dispose 方法之间)中没有发生异常,则该范围所参与的事务就会得以继续。如果事务范围内确有异常发生,则该范围所参与的事务就会回滚。如果应用程序完成了希望在事务中执行的所有任务,则会调用一次 Complete 方法。这会通知事务管理器可以提交事务了。如果未调用 Complete 方法,则事务会结束。有关事务范围的更多信息,请参见 MSDN documentation(MSDN 文档)。
.NET Framework 中的事务范围实现
System.Transactions 命名空间属于 Microsoft .NET Framework 2.0 版 版本 3.0、3.5 和 4 的一部分。它可以提供一种事务框架,该事务框架与 ADO.NET 和 SQL Server 公共语言运行时 (CLR) 集成完全集成。System.Transactions.transactionscope 类通过在分布式事务中隐式登记连接,使代码块可以进行事务处理。在 TransactionScope 标记的代码块结尾处,可调用 Complete 方法。如果在调用 Dispose 方法之前未调用 Complete 方法,则该事务会停止。如果引发异常,则认为该事务已停止。
有关详细信息,请参阅 https://msdn2.microsoft.com/en-us/library/ms172070(VS.80).aspx。
System.Transactions 的限制
.NET Compact Framework 2.0 不支持 System.Transactions 命名空间。因此,实现只适用于 Windows 桌面操作系统,且会与 .NET Framework 2.0、.NET Framework 3.0、.NET Framework 3.5 或 .NET Framework 4 对应。
请注意,如果有超时,System.Transactions 基础结构将从独立线程调用 Rollback。主线程将不会知道发生在独立线程中的回滚。持续时间较长的事务可能会发生非确定性行为和部分提交的情况。若要解决这个问题,请在创建事务范围对象时延长该对象的时间跨度。
在事务范围中,如果没有其他事务管理器登记到该事务范围内,则只有一个 SqlCeConnection 对象可登记到该范围内。
如果某个连接在事务范围外打开,且必须将其登记到现有事务范围内,则可以使用 EnlistTransaction 方法来完成此操作。
TransactionScope 实现
SQL Server Compact 可作为一种资源登记到 System.Transactions 基础结构中。
默认情况下,如果在事务范围中已登记的连接上打开多个命令,则这些命令会登记到当前事务上下文中。也可以打开未登记到事务范围中的连接。但这会导致命令未登记。事务范围内 SQL Server Compact 事务的默认事务类型为可序列化类型。
因为 SQL Server Compact 不支持分布式事务,所以无法将两个或更多的连接登记到同一事务范围内,或登记到共享同一环境事务范围的嵌套事务范围内。
不允许在事务范围内存在已登记连接的显式事务。
不支持隐式登记连接。若要登记到事务范围中,您可以执行以下操作:
打开事务范围中的一个连接。
如果已打开该连接,请对连接对象调用 EnlistTransaction 方法。
SQL Server Compact 的限制
SQL Server Compact 和 SQL Server 在事务范围方面具有以下不同点:
SQL Server Compact 不支持分布式事务。因此,本地事务将不会自动提升为完全可分布的事务。
SQL Server Compact 支持并行事务,即使有多个活动结果集也是如此,而 SQL Server 却不支持并行事务。
TransactionScope 示例 1
下面的示例演示如何使用 TransactionScope 登记,然后提交事务。
using (TransactionScope transScope = new TransactionScope())
{
using (SqlCeConnection connection1 = new
SqlCeConnection(connectString1))
{
/* Opening connection1 automatically enlists it in the
TransactionScope as a lightweight transaction. */
connection1.Open();
// Do work in the connection.
}
// The Complete method commits the transaction.
transScope.Complete();
}
TransactionScope 示例 2
下面的示例演示如何使用 TransactionScope 在数据库中创建两个表。
static void Setup(String strDbPath)
{
/* Delete the database file if it already exists. We will create a new one. */
if (File.Exists(strDbPath))
{
File.Delete(strDbPath);
}
// Create a new database.
SqlCeEngine engine = new SqlCeEngine();
engine.LocalConnectionString = @"Data source = " + strDbPath;
engine.CreateDatabase();
engine.Dispose();
}
/* This function creates two tables in the specified database. Before creating the tables, it re-creates the database.
These tables are created in a TransactionScope, which means that either both of them will be created or not created at all. */
static void CreateTablesInTransaction(String strDbPath)
{
/* Create the connection string. In order to have the connection enlisted into the TransactionScope, the Enlist property in the connection string must be explicitly set to true. */
String strConn = @"Data source = " + strDbPath + ";Enlist=true";
SqlCeConnection conn = new SqlCeConnection(strConn);
try
{
Setup(strDbPath); // Create a new database for our tables.
using (TransactionScope scope = new TransactionScope())
{
/* To enlist a connection into a TransactinScope, specify 'Enlist=true' in its connection string and open the connection in the scope of that TransactionScope object. */
conn.Open();
// Create the tables.
SqlCeCommand command = conn.CreateCommand();
command.CommandText = @"create table t1(col1 int)";
command.ExecuteNonQuery();
command.CommandText = @"create table t2(col1 int)";
command.ExecuteNonQuery();
/* If this statement is executed and the TransactionScope has not timed out, t1 and t2 will be created in the specified database. */
scope.Complete();
}
}
catch (SqlCeException e)
{
Console.WriteLine(e.Message);
}
finally
{
if (conn.State != System.Data.ConnectionState.Closed)
{
conn.Close();
}
conn.Dispose();
}
}
TransactionScope 示例 3
下面的示例演示如何在 TransactionScope 中向两个表插入值。
/* This function assumes that tables t1(col1 int) and t2(col1 int) are already created in the specified database. The condition for the following function is this:
If INSERTs into the first table succeed, then INSERT into the second table. However, if the INSERTs into the second table fail, roll back the inserts in the second table but do not roll back the inserts in the first table. Although this can also be done by way of regular transactions, this function demonstrates how to do it using TransactionScope objects. */
static void CreateTableAndInsertValues(String strDbPath)
{
/* Create the connection string. To have the connection enlisted into the TransactionScope, the Enlist property in the connection string must be explicitly set to true. */
String strConn = @"Data source = " + strDbPath + ";Enlist=true";
SqlCeConnection conn1 = new SqlCeConnection(strConn);
SqlCeConnection conn2 = new SqlCeConnection(strConn);
try
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
conn1.Open();
SqlCeCommand command1 = conn1.CreateCommand();
command1.CommandText = @"insert into t1(col1) values(1)";
command1.ExecuteNonQuery();
command1.CommandText = @"insert into t1(col1) values(2)";
command1.ExecuteNonQuery();
/* If this statement is executed and the TransactionScope has not timed out, two records will be inserted into table 1. */
scope.Complete();
try
{
using (TransactionScope scopeInner = new TransactionScope(TransactionScopeOption.RequiresNew))
{
conn2.Open();
SqlCeCommand command2 = conn2.CreateCommand();
command2.CommandText = @"insert into t2(col1) values(1)";
command2.ExecuteNonQuery();
command2.CommandText = @"insert into t2(col1) values(2)";
command2.ExecuteNonQuery();
/* If this statement is run and the TransactionScope has not timed out, two records will be inserted into table 2. */
scopeInner.Complete();
}
}
catch (SqlCeException e)
{
Console.WriteLine(@"Exception in Inner block: " + e.Message);
}
}
}
catch (SqlCeException e)
{
Console.WriteLine(@"Exception in Outer block: " + e.Message);
}
finally
{
// Close both the connections.
if (conn1.State != System.Data.ConnectionState.Closed)
{
conn1.Close();
}
if (conn2.State != System.Data.ConnectionState.Closed)
{
conn2.Close();
}
conn1.Dispose();
conn2.Dispose();
}
}