处理事务的另一种方法是使用System.Transactions。这些类自从.NET Framework 2.0开始就可以使用,但是在.NET Core 1.1中还不可用。在.NET Core2.1中,这些类型又回来了,可以从ADO.NET(从System.Data.SqlClient的4.5版本开始)和Entity Framework Core 2.1中使用。
System.Transactions名称空间为事务定义了几个类。可能最重要的类是Transaction。它可以用于直接访问环境事务,提供关于事务的信息,以及在发生故障时启动回滚。下表描述了Transaction类的成员:
示例应用程序(.NET Core Console App)显示,System.Transactions系统的特性使用了以下依赖项和名称空间:
依赖项
Microsoft.Extensions.Configuration
Microsoft.Extensions.Configuration.Json
System.Data.SqlClient
名称空间
Microsoft.Extensions.Configuration
System
System.Data.SqlClient
System.IO
System.Linq
System.Threading.Tasks
System.Transactions
代码示例定义了一个Utilities类,其中的一些辅助方法在不同示例中使用。
using System; using System.Linq; using System.Transactions; namespace SystemTransactionSamples { public class Utilities { public static bool AbortTx() { Console.WriteLine("Abort the transaction (y/n)?"); return Console.ReadLine().ToLower().Equals("y"); } public static void DisplayTransactionInformation(string title, TransactionInformation info) { if (info == null) throw new ArgumentNullException(nameof(title)); Console.WriteLine(title); Console.WriteLine($"Creation time: {info.CreationTime:T}"); Console.WriteLine($"Status: {info.Status}"); Console.WriteLine($"Local Id: {info.LocalIdentifier}"); Console.WriteLine($"Distributed Id: {info.DistributedIdentifier}"); } public static string RandomIsbn() => string.Join("",Guid.NewGuid().ToString().ToCharArray().Take(15)); } }注意:
示例代码询问用户是否应该终止事务。这适用于演示,但是在实际应用程序的事务中不应该有用户交互。当事务处于活动状态时,锁定记录。我们不希望妨碍其他用户工作,原因是一个用户因为这些锁打开了锁。
还需要注意事务超时。如果用户输入占用的时间超过事务超时时间,事务将被中止。
这个示例使用与前面示例相同的数据库,但是有一个重要的更改。这次Book类型定义为从Books表中映射信息:
public class Book { public int Id { get; set; } public string Title { get; set; } public string Publisher { get; set; } public string Isbn { get; set; } public DateTime? ReleaseDate { get; set; } }BookData类将Book类型映射到数据库,并实现了AddBookAsync方法,以向Books表添加新记录。该实现与以前在调用ExecuteNonQuety方法插入新记录时看到的实现类似,但有一个重要的区别。这次System.Transactions.Transaction使用SqlConnection方法EnlistTransaction征募。这样,SqlConnection将参与该事务的结果:
public class BookData { public async Task AddBookAsync(Book book, Transaction tx) { using (SqlConnection connection = new SqlConnection(GetConnectionString())) { string sql = "INSERT INTO [ProCSharp].[Books] ([Title],[Publisher],[Isbn],[ReleaseDate]) "+ "VALUES (@tITLE,@Publisher,@Isbn,@ReleaseDate)"; await connection.OpenAsync(); if (tx!=null) { connection.EnlistTransaction(tx); } SqlCommand command = connection.CreateCommand(); command.CommandText = sql; command.Parameters.AddWithValue("@Title",book.Title); command.Parameters.AddWithValue("@Publisher",book.Publisher); command.Parameters.AddWithValue("@Isbn",book.Isbn); command.Parameters.AddWithValue("@ReleaseDate",book.ReleaseDate); await command.ExecuteNonQueryAsync(); } } public string GetConnectionString() { var configurationBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("config.json"); IConfiguration config = configurationBuilder.Build(); return config["Data:DefaultConnection:ConnectionString"]; } }1. 可提交的事务
Transaction类不能以编程方式提交;它没有提交事务的方法。基类Transaction只支持中止事务。唯一支持提交事务的类是类CommittableTransaction。
在CommittableTransactionAsync方法中,首先创建一个类型为CommittableTransaction的事务,并向控制台显示信息。然后创建一个Book对象,该对象在AddBookAsync方法中写入数据库。如果在事务外部验证数据库中的记录,则在事务完成之前无法看到添加的图书。如果事务失败,就会出现回滚,并且不会将图书写到数据库中。
在调用AddBookAsync方法之后,调用AbortTx方法来询问用户是否应该中止事务。如果用户中止,则抛出ApplicationExceptoin类型的异常,在catch块中,通过调用Transaction类的Rollback方法执行回滚。记录不会写入数据库。如果用户没有中止,Commit方法将提交事务,并提交事务的最终状态:
static async Task CommittableTransactionAsync() { var tx = new CommittableTransaction(); DisplayTransactionInformation("TX Created",tx.TransactionInformation); try { var book = new Book { Title = "A Dog in The House", Publisher = "Pet Show", Isbn = RandomIsbn(), ReleaseDate = new DateTime(2020,07,02) }; var data = new BookData(); await data.AddBookAsync(book,tx); if (AbortTx()) { throw new ApplicationException("transaction abort by the user"); } tx.Commit(); } catch (Exception ex) { Console.WriteLine(ex.Message); tx.Rollback(); } DisplayTransactionInformation("TX Completed",tx.TransactionInformation); }如下面的应用程序输出所示,事务是活动的,并且具有一个本地标识符。此外,用户选择中止事务。事务完成后,可以看到中止状态:
TX Created Creation time: 4:41:44 Status: Active Local Id: 4e844121-86c5-4470-8c3b-87e6bd51c0af:1 Distributed Id: 00000000-0000-0000-0000-000000000000 Abort the transaction (y/n)? y transaction abort by the user TX Completed Creation time: 4:41:44 Status: Aborted Local Id: 4e844121-86c5-4470-8c3b-87e6bd51c0af:1 Distributed Id: 00000000-0000-0000-0000-000000000000接下来是应用程序的第二个输出,用户不会中止事务。事务的状态是Committed,数据写入数据库:
TX Created Creation time: 4:43:57 Status: Active Local Id: 2c7dad06-597e-4128-88f0-af0322585eb3:1 Distributed Id: 00000000-0000-0000-0000-000000000000 Abort the transaction (y/n)? n TX Completed Creation time: 4:43:57 Status: Committed Local Id: 2c7dad06-597e-4128-88f0-af0322585eb3:1 Distributed Id: 00000000-0000-0000-0000-0000000000002. 依赖事务
对于依赖事务,可以在多个任务或线程之间影响一个事务。依赖事务依赖于另一个事务,并影响事务的结果。
示例方法DependentTransaction首先使用CommittableTransaction创建根事务。此事务对象使用方法DependentClone创建一个依赖事务。该DependentTransaction在本地函数UsingDependentTransactionAsync中传递给一个单独的任务。依赖事务可以标记为已完成,就像调用Complete方法一样。
DependentClone方法需要DependentCloneOption类型的参数,该参数是一个枚举,其值为BlockCompleteUntilComplete和RollbackIfNotComplete。如果根事务在依赖事务之前完成,则此选项非常重要。将该选项设置为RollbackIfNotComplete,如果依赖事务没有在根事务的Commit方法之前调用Complete方法,则事务将中止。将该选项设置为BlockCommitUntilComplete,方法Commit就会等待(阻塞),直到所有依赖事务都定义了结果为止。
接下来,如果用户不中止事务,则调用CommittableTransaction类的Commit方法:
static void DependentTransaction() { async Task UsingDependentTransactionAsync(DependentTransaction dependentTransaction) { dependentTransaction.TransactionCompleted += (sender, e) => { DisplayTransactionInformation("Dependent TX completed", e.Transaction.TransactionInformation); }; DisplayTransactionInformation("Dependent TX Start", dependentTransaction.TransactionInformation); await Task.Delay(10000); dependentTransaction.Complete(); DisplayTransactionInformation("Dependent TX send complete", dependentTransaction.TransactionInformation); } var tx = new CommittableTransaction(); DisplayTransactionInformation("Root TX created", tx.TransactionInformation); try { DependentTransaction depTransaction = tx.DependentClone(DependentCloneOption.BlockCommitUntilComplete); Task task = Task.Run(() => UsingDependentTransactionAsync(depTransaction)); if (AbortTx()) { throw new ApplicationException("transaction abort by the user"); } tx.Commit(); } catch (Exception ex) { Console.WriteLine(ex.Message); tx.Rollback(); } DisplayTransactionInformation("TX completed", tx.TransactionInformation); }应用程序的以下输出显示了根事务及其标识符。因为选项DependentCloneOption.BlockCommitUntilComplete,根事务在Commit方法中等待,直到定义了依赖事务的结果为止。一旦依赖事务完成,就提交事务:
Root TX created Creation time: 8:47:29 Status: Active Local Id: 2d325ac3-77e3-4d21-b719-38e432605711:1 Distributed Id: 00000000-0000-0000-0000-000000000000 Abort the transaction (y/n)? Dependent TX Start Creation time: 8:47:29 Status: Active Local Id: 2d325ac3-77e3-4d21-b719-38e432605711:1 Distributed Id: 00000000-0000-0000-0000-000000000000 n Dependent TX completed Creation time: 8:47:29 Status: Committed Local Id: 2d325ac3-77e3-4d21-b719-38e432605711:1 Distributed Id: 00000000-0000-0000-0000-000000000000 Dependent TX send complete Creation time: 8:47:29 Status: Committed Local Id: 2d325ac3-77e3-4d21-b719-38e432605711:1 Distributed Id: 00000000-0000-0000-0000-000000000000 TX completed Creation time: 8:47:29 Status: Committed Local Id: 2d325ac3-77e3-4d21-b719-38e432605711:1 Distributed Id: 00000000-0000-0000-0000-0000000000003. 环境事务
System.Transactions名称空间中类的最大优势是环境事务特性。对于环境事务,不需要手动获取与事务的连接;这是由支持环境事务的资源自动完成的。
环境事务与当前线程关联。可以使用静态属性Transaction.Current获取并设置环境事务。支持环境事务的API检查此属性,以获得环境事务,并与事务合并。ADO.NET连接支持环境事务。
可以创建一个CommittableTransaction对象并将其分配给Transaction.Current属性,来初始化环境事务。另一种方法(通常是首选方法)是使用TransactionScope类。TransactionScope的构造函数会创建一个环境事务。
注意:
如果ADO.NET连接不应该与环境事务合并,则可以用连接字符串设置值Enlist=false。
为了使用TransactionScope,创建了另一个AddBook方法,它只向Books表添加一条记录,而不显示地向事务注册:
public void AddBook(Book book) { using (var connection = new SqlConnection()) { string sql = "INSERT INTO [ProCSharp].[Books] ([Title],[Publisher],[Isbn],[ReleaseDate]) " + "VALUES (@TITLE,@Publisher,@Isbn,@ReleaseDate)"; connection.Open(); var command = connection.CreateCommand(); command.CommandText = sql; command.Parameters.AddWithValue("@Title", book.Title); command.Parameters.AddWithValue("@Publisher",book.Publisher); command.Parameters.AddWithValue("@Isbn",book.Isbn); command.Parameters.AddWithValue("@ReleaseDate",book.ReleaseDate); command.ExecuteNonQuery(); } }TransactionScope的重要方法是Complete和Dispose。Complete方法为作用域设置成功位,如果作用域是根作用域,那么Dispose方法将结束作用域,并提交或回滚事务。
因为TransactionScope类实现了IDisposable接口,所以可以使用using语句定义作用域。默认构造函数创建一个新事务。下面的代码片段在创建TransactionScope实例之后,立即将调用DisplayTransactionInformation的lambda表达式分配给TransactionCompleted事件,以便在事务完成时获取信息。在此事件触发之前,在创建事务之后,访问Transaction.Current属性,以在控制台上显示环境事务的当前状态。然后,创建一个新的Book对象,并调用先前创建的AddBook方法。如果用户没有中止事务,则调用TransactionScope的Complete方法。
using块的末尾调用TransactionScope的Dispose方法。如果调用了Complete方法,并且参与事务的所有其他方(例如,SQL连接)都设置了成功位,则提交事务。如果任何一方未成功处理包含TransactionScope的事务,或未调用Complete,则中止事务:
static void AmbientTransaction() { using (var scope = new TransactionScope()) { Transaction.Current.TransactionCompleted += (sender,e) => { DisplayTransactionInformation("TX completed",e.Transaction.TransactionInformation); }; DisplayTransactionInformation("Ambient TX created",Transaction.Current.TransactionInformation); var book = new Book { Title = "Cats in the House", Publisher = "Pet Show", Isbn = RandomIsbn(), ReleaseDate = new DateTime(2020,07,02) }; var data = new BookData(); data.AddBook(book); if (!AbortTx()) { scope.Complete(); } else { Console.WriteLine("transaction abort by the user"); } }//scope.Dispose(); }运行应用程序时,可以在创建TransactionScope类的实例之后看到一个活动的环境事务。应用程序的最后一个输出是TransactionComplete事件处理程序的输出,以显示已完成的事务状态:
Ambient TX created Creation time: 10:37:13 Status: Active Local Id: b5ae4cbc-ae23-41fc-9f15-a67fcc4604d3:1 Distributed Id: 00000000-0000-0000-0000-000000000000 Abort the transaction (y/n)? n TX completed Creation time: 10:37:13 Status: Committed Local Id: b5ae4cbc-ae23-41fc-9f15-a67fcc4604d3:1 Distributed Id: 00000000-0000-0000-0000-000000000000或用户执行中止操作:
Ambient TX created Creation time: 10:37:58 Status: Active Local Id: 215fd0ac-7d5c-407a-9d2d-ede78ff1fd16:1 Distributed Id: 00000000-0000-0000-0000-000000000000 Abort the transaction (y/n)? y transaction abort by the user TX completed Creation time: 10:37:58 Status: Aborted Local Id: 215fd0ac-7d5c-407a-9d2d-ede78ff1fd16:1 Distributed Id: 00000000-0000-0000-0000-0000000000004. 嵌套作用域和环境事务
对于TransactionScope类,还可以嵌套作用域。嵌套的作用域环境事务可以直接位于外部作用域内,也可以位于内部作用域内。内部作用域可以使用与外部作用域相同的事务,抑制事务,或者创建独立于外部作用域的新事务。对该作用域的需求由TransactionScopeOption枚举定义,该枚举传递给TransactionScope类的构造函数。
下表描述了TransactionScope枚举可用的值和相应功能。
下一个示例定义了两个作用域。使用选项TransactionScope.RequiresNew,内部作用域配置为需要一个新事务:
static void NestedScopes() { using (var scope1 = new TransactionScope()) { Transaction.Current.TransactionCompleted += (sener,e) => { DisplayTransactionInformation("TX completed",e.Transaction.TransactionInformation); }; DisplayTransactionInformation("Ambient TX created",Transaction.Current.TransactionInformation); var book1 = new Book { Title = "Dogs in The House", Publisher = "Pet Show", Isbn = RandomIsbn(), ReleaseDate = new DateTime(2020,7,3) }; var data1 = new BookData(); data1.AddBook(book1); using (var scope2 = new TransactionScope(TransactionScopeOption.RequiresNew)) { Transaction.Current.TransactionCompleted += (sender,e) => { DisplayTransactionInformation("Inner TX completed",e.Transaction.TransactionInformation); }; DisplayTransactionInformation("Inner TX created",Transaction.Current.TransactionInformation); var book2 = new Book { Title = "Dogs and Cats in The House", Publisher = "Pet Show", Isbn = RandomIsbn(), ReleaseDate = new DateTime(2020,7,3) }; var data2 = new BookData(); data2.AddBook(book2); scope2.Complete(); } scope1.Complete(); } }在运行应用程序时,可以看到外部作用域和内部作用域的不同事务标识符,其中内部作用域在GUID上附加:2,外部作用域在GUID上附加:1。这些事务彼此独立:
Ambient TX created Creation time: 6:09:29 Status: Active Local Id: ceb6c7f6-feee-4af7-ac35-00882619aa7d:1 Distributed Id: 00000000-0000-0000-0000-000000000000 Inner TX scope Creation time: 6:09:29 Status: Active Local Id: ceb6c7f6-feee-4af7-ac35-00882619aa7d:2 Distributed Id: 00000000-0000-0000-0000-000000000000 Inner TX completed Creation time: 6:09:29 Status: Committed Local Id: ceb6c7f6-feee-4af7-ac35-00882619aa7d:2 Distributed Id: 00000000-0000-0000-0000-000000000000 TX completed Creation time: 6:09:29 Status: Committed Local Id: ceb6c7f6-feee-4af7-ac35-00882619aa7d:1 Distributed Id: 00000000-0000-0000-0000-000000000000如果将内部作用域的创建更改为使用TransactionScopeOption.Required,就可以看到同样的事务用于外部和内部作用域:
Ambient TX created Creation time: 3:22:27 Status: Active Local Id: 8b63bd68-7b71-4432-9215-af19c0bed7bf:1 Distributed Id: 00000000-0000-0000-0000-000000000000 Inner TX scope Creation time: 3:22:27 Status: Active Local Id: 8b63bd68-7b71-4432-9215-af19c0bed7bf:1 Distributed Id: 00000000-0000-0000-0000-000000000000 TX completed Creation time: 3:22:27 Status: Committed Local Id: 8b63bd68-7b71-4432-9215-af19c0bed7bf:1 Distributed Id: 00000000-0000-0000-0000-000000000000 Inner TX completed Creation time: 3:22:27 Status: Committed Local Id: 8b63bd68-7b71-4432-9215-af19c0bed7bf:1 Distributed Id: 00000000-0000-0000-0000-000000000000