做者:依乐祝html
今天在写CzarCms的UnitOfWork的使用使用到了这个TransactionScope事务,所以对它进行了相关资料的查阅并记录以下,但愿对大伙在.NET Core中使用有所帮助。数据库
您是否曾尝试使用C#代码来实现事务?一般,咱们在SQL中一次执行多个Insert / Update语句的话可能就会使用到事务。事务遵循ACID(原子性,一致性,隔离性,持久性)规则,这样全部的语句要么所有执行成功要么所有被取消并执行回滚操做。 而咱们今天要讲的TransactionScope则能够容许咱们在应用程序级别实现这个过程。在某些状况下,您可能须要在同一个数据库甚至多个数据库(分布式事务)中执行不一样的操做,或者因为某些其余约束,它没法在数据库级别来完成,或者应用程序的开发人员对数据库的接触较少,那么这时候TransactionScope将会让你游刃有余。c#
TransactionScope做为System.Transactions的一部分被引入到.NET 2.0。同时SqlClient for .NET Core 从 2.1 及以上版本开始提供对System.Transactions的支持 。 它是一个类,它提供了一种简单的方法,能够将一组操做做为事务的一部分来进行处理,而没必要担忧场景背后的复杂性。若是某个操做在执行的过程当中失败的话,则整个事务将失败并执行回滚操做,从而撤消已完成的全部操做。全部这些都将由框架处理,从而确保数据的一致性。windows
要使用它,您须要添加System.Transactions的引用,若是你使用的是.net core的话。这个引用被包含在netcoreapp2.2\System.Transactions.Local.dll
中, 该引用是框架库的一部分(一般默认状况下不会自动添加)。添加后,在咱们想要使用它的地方添加名称空间 System.Transactions便可。代码以下所示:服务器
try { using (TransactionScope scope = new TransactionScope()) { // Do Operation 1 // Do Operation 2 //... // 若是全部的操做都执行成功,则Complete()会被调用来提交事务 // 若是发生异常,则不会调用它并回滚事务 scope.Complete(); } } catch (ThreadAbortException ex) { // 处理异常 }
在上面的代码中咱们能够看到咱们在建立TransactionScope实例时使用了using
语句块及Disposable块,它确保了当dispose离开块并结束事务范围时调用dispose来进行资源的释放。
在一个Transaction范围中,咱们能够作多个链接甚至连接到不一样数据库的操做的,以下所示:网络
using (TransactionScope scope = new TransactionScope()) { using (con = new SqlConnection(conString1)) { con.Open(); // 执行操做 1 // 执行操做 2 //... } using (con = new SqlConnection(conString2)) { con.Open(); // 执行操做 1 // 执行操做 2 //... } scope.Complete(); }
下面咱们使用两个不一样的数据库链接字符串来链接不一样的数据库。固然咱们也能够根据咱们的业务要求使用尽量多数据库。咱们也能够再事务中嵌套事务。以下代码所示:app
public void DoMultipleTransaction() { try { using (TransactionScope scope = new TransactionScope()) { using (con = new SqlConnection(conString1)) { con.Open(); // 执行操做1 } OtherTransaction(); scope.Complete(); } } catch (ThreadAbortException ex) { // 处理异常 } } private void OtherTransaction() { using (TransactionScope scope = new TransactionScope()) { using (con = new SqlConnection(conString2)) { con.Open(); // 执行操做 } scope.Complete(); } }
这里最顶层的事务范围称为根范围。另外这里须要注意的是即便经过调用scope.Complete()完成内部事务(上面的OtherTransaction ),若是因为各类缘由没法调用rootscope complete,那么整个事务也将被回滚包括内部的事务。框架
*注意:执行分布式trsanctions时,您可能会收到如下异常之一*异步
这两个错误都是因为一样的缘由,第一个是在数据库和应用程序是同一个服务器时发生的,而在另外一个则是服务跟数据库分别部署在两台服务器上。对于同一台服务器,请转到run-> cmd-> services.msc。运行名为Distributed Transaction Coordinator的服务并自动启动启动类型,以便在系统从新启动时再次启动它。对于2,你可能须要参照这个连接的内容进行相应的设置
TransactionScope 类提供了多个重载构造函数,它们接受 TransactionScopeOption 类型的枚举,而该枚举定义事务范围行为。
TransactionScope对象有如下三个选项:
若是用 Required] 实例化范围而且存在环境事务,则该范围会联接该事务。 相反,若是不存在环境事务,该范围就会建立新的事务并成为根范围。 这是默认值。 在使用 Required时,不管范围是根范围仍是仅联接环境事务,该范围中的代码都不须要有不一样的行为。 该代码在这两种状况下的行为应相同。
若是用 RequiresNew 实例化范围,则它始终为根范围。 它会启动一个新事务,而且其事务成为该范围中的新环境事务。
若是用 Suppress 实例化范围,则不管是否存在环境事务,范围都从不参与事务。 始终使用此值实例化的做用域具备null做为其环境事务。
下面来让咱们看一组实例代码:
using (TransactionScope scope = new TransactionScope()) { // 联接环境事务,或者在环境事务不存在的状况下建立新的环境事务。 using (TransactionScope scope1 = new TransactionScope(TransactionScopeOption.Required)) { // Do Operation scope1.Complete(); } //成为新的根范围,也就是说,启动一个新事务并使该事务成为其本身范围中的新环境事务。 using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.RequiresNew)) { // Do Operation scope2.Complete(); } //根本不参与事务。 所以没有环境事务。 using (TransactionScope scope3 = new TransactionScope(TransactionScopeOption.Suppress)) { // Do Operation scope3.Complete(); } scope.Complet
在这里,咱们使用不一样的TransactionScopeOptions在父事务下建立了三个事务。默认状况下,范围是required ,这里父事务就是采用的这个默认参数进行建立的。它是一个建立新事务的根范围,并将其标记为环境事务。scope1也是使用required建立的,由于咱们已经有了一个环境事务(范围),因此它加入到父事务中。scope2是使用RequiresNew选项建立的,这意味着它是一个独立于环境事务处理的新事务。scope3是用suppress建立的选项,这意味着它不参与任何环境事务。不管环境事务是否成功执行,它都会被执行。父(全局)范围完成后,将提交全部环境事务。
EF Core 依赖数据库提供程序以实现对 System.Transactions 的支持。 虽然支持在 .NET Framework 的 ADO.NET 提供程序之间十分常见,但最近才将 API 添加到 .NET Core,所以支持并未获得普遍应用。 若是提供程序未实现对 System.Transactions 的支持,则可能会彻底忽略对这些 API 的调用。 SqlClient for .NET Core 从 2.1 及以上版本开始支持 System.Transactions。若是尝试在低版本中 如.NET Core 2.0中尝试使用该功能将引起异常。
自版本 2.1 起,.NET Core 中的 System.Transactions 实现将不包括对分布式事务的支持,所以不能使用 TransactionScope
或 CommittableTransaction
来跨多个资源管理器协调事务。主要是不依赖windows中的mstsc功能。
异步方法使用时须要注意:
在下面的例子中,咱们在TransactionScope
内部使用await
。
using(var scope = new TransactionScope()) { var groups = await Context.ProductGroups.ToListAsync()。ConfigureAwait(false); }
看起来没有问题,但它会抛出一个 System.InvalidOperationException:``A TransactionScope must be disposed on the same thread that it was created.
缘由是默认状况下TransactionScope
不会从一个线程切换到另外一个线程。为了解决这个问题,咱们必须使用 TransactionScopeAsyncFlowOption.Enabled
:
using(var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { var groups = await Context.ProductGroups.ToListAsync()。ConfigureAwait(false); }
如今应该能够了吧?这取决于下面的状况。
若是咱们使用和不使用TransactionScopeAsyncFlowOption这个
选项的时候都使用了相同的数据库链接,而且第一次执行的时候没有使用这个选项,那么咱们会获得另外一个异常: System.InvalidOperationException:``Connection currently has transaction enlisted. Finish current transaction and retry.
换句话说,因为第一个访问的缘由,第二个会话将会失败。以下代码所示:
try { using (var scope = new TransactionScope()) { // We know this one - System.InvalidOperationException: // TransactionScope必须放在与建立它相同的线程上。 var groups = await Context.ProductGroups.ToListAsync().ConfigureAwait(false); } } catch (Exception e) { // error handling } using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { // Implemented correctly but throws anyways // System.InvalidOperationException: // 当前链接已经被记录。完成当前事务并重试。 var groups = await Context.ProductGroups.ToListAsync().ConfigureAwait(false); }
想象一下,若是第一个调用是在第三方库或您正在使用的框架中完成的,二您不了解其中的代码 - 若是您以前没有看到此错误,那么你讲无从下手来解决这个问题。
本文带着你们熟悉了一遍TransactionScope并对其使用进行了介绍!同时介绍了在.NET Core中使用TransactionScope的一些注意事项!但愿对你们有所帮助。另附上.NET Core实战项目交流群:637326624