欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

TransactionScope事务处理方法介绍及.NET Core中的注意事项

程序员文章站 2022-06-25 08:13:41
作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/10170712.html 今天在写CzarCms的UnitOfWork的使用使用到了这个TransactionScope事务,因此对它进行了相关资料的查阅并记录如下,希望对大伙在.NET Core中使用有所 ......

作者:依乐祝

原文链接:

今天在写czarcms的unitofwork的使用使用到了这个transactionscope事务,因此对它进行了相关资料的查阅并记录如下,希望对大伙在.net core中使用有所帮助。

写在前面

您是否曾尝试使用c#代码来实现事务?通常,我们在sql中一次执行多个insert / update语句的话可能就会使用到事务。事务遵循acid(原子性,一致性,隔离性,持久性)规则,这样所有的语句要么全部执行成功要么全部被取消并执行回滚操作。 而我们今天要讲的transactionscope则可以允许我们在应用程序级别实现这个过程。在某些情况下,您可能需要在同一个数据库甚至多个数据库(分布式事务)中执行不同的操作,或者由于某些其他约束,它无法在数据库级别来完成,或者应用程序的开发人员对数据库的接触较少,那么这时候transactionscope将会让你游刃有余。

什么是transactionscope呢?

transactionscope作为system.transactions的一部分被引入到.net 2.0。同时sqlclient for .net core 从 2.1 及以上版本开始提供对system.transactions的支持 。 它是一个类,它提供了一种简单的方法,可以将一组操作作为事务的一部分来进行处理,而不必担心场景背后的复杂性。如果某个操作在执行的过程中失败的话,则整个事务将失败并执行回滚操作,从而撤消已完成的所有操作。所有这些都将由框架处理,从而确保数据的一致性。

如何使用transactionscope呢?

要使用它,您需要添加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();
}

下面我们使用两个不同的数据库连接字符串来连接不同的数据库。当然我们也可以根据我们的业务要求使用尽可能多数据库。我们也可以再事务中嵌套事务。如下代码所示:

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时,您可能会收到以下异常之一*

  1. 服务器上的msdtc不可用
  2. 已禁用分布式事务管理器(msdtc)的网络访问。

这两个错误都是由于同样的原因,第一个是在数据库和应用程序是同一个服务器时发生的,而在另一个则是服务跟数据库分别部署在两台服务器上。对于同一台服务器,请转到run-> cmd-> services.msc。运行名为distributed transaction coordinator的服务并自动启动启动类型,以便在系统重新启动时再次启动它。对于2,你可能需要参照这个的内容进行相应的设置

transactionscope 类提供了多个重载构造函数,它们接受 transactionscopeoption 类型的枚举,而该枚举定义事务范围行为。

transactionscope对象有以下三个选项:

  • required:联接环境事务,或者在环境事务不存在的情况下创建新的环境事务。
  • requiresnew:成为新的根范围,也就是说,启动一个新事务并使该事务成为其自己范围中的新环境事务。
  • suppress:根本不参与事务。 因此没有环境事务。

如果用 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创建的选项,这意味着它不参与任何环境事务。无论环境事务是否成功执行,它都会被执行。父(全局)范围完成后,将提交所有环境事务。

注意点

  1. 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. 自版本 2.1 起,.net core 中的 system.transactions 实现将不包括对分布式事务的支持,因此不能使用 transactionscopecommittabletransaction 来跨多个资源管理器协调事务。主要是不依赖windows中的mstsc功能。

  3. 异步方法使用时需要注意:

    在下面的例子中,我们在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