Transaction2

5. 事务状态 TransactionInformation

上面讲解过事务分为本地事务与分布式事务,而Transaction类的TransactionInformation是事务状态的记录,它可以跟踪事务动作,分辨事务现处的状态,记录本地事务与分布式事务的Guid

TransactionInformation有两个重要成员:

public class TransactionInformation
{
      //返回分布式事务标识符
      public Guid DistributedIdentifier 
      {get;}

      //返回本地事务标识符
      public string LocalIdentifier
      {get;}
}

  

LocalIndentifier 是本地事务的标识符,它可以获取本地事务管理器(LTM)的ID,并且注意只要事务存在,它的值就永远不会是null。它包含两个部分,一个是LTM的 Guid,它是应用程序中的唯一值,代表了现存应用程序域分配的LTM。另一部分是一个可变量,代表了当时该应用程序域中的事务数量。

例如:3427dec9-4abc-34cc-9edf-30ad835c33k3:3

其中3427dec9-4abc-34cc-9edf-30ad835c33k3是此本地事务管理器的Guid,在事务启动后,此值都是不变的,而 “3” 代表此刻该应用程序域中存在 “3” 个本地事务。

 

DistributedIndentifier 是分布式事务的标识符,在普通情况下DistributedIndentifier的值都为Guid.Empty。但当LTM或KTM事务被提升到分布式 事务时,DistributedIndentifier就会产生。最重的是,在同一个分布式事务管理器当中,即使事务跨越服务边界,分布式ID都是一致 的。DistributedIndentifier是分布式事务的唯一标识符,它的使用方法在后面 “事务的传播”一节将详细介绍。

 

 

在TransactionManager类中,还提供了一个事件DistributedTransactionStarted专门用于测试分布式事务的变化。

    class Program
    {
        static void Main(string[] args)
        {
            using (TransactionScope scope = new TransactionScope())
            {
                TransactionManager.DistributedTransactionStarted += OnDistributedTransactionStarted;
                ............
                scope.Complete();
            }
            Console.ReadKey();
        }

        //当执行分布式事务是就会启动此方法显示事务信息
        static void OnDistributedTransactionStarted(object sender, TransactionEventArgs args)
        {
            Transaction transaction = args.Transaction;
            Console.WriteLine("Distributed Transaction Started!\n  DistributedIndentifier:"
                 +transaction.TransactionInformation.DistributedIdentifier);
        }
    }

 

 

 

 

在ADO.NET中实现事务

1. ADO.NET事务的主要成员

需要使用事务管理的情况很多,在数据层使用得特别广泛,几乎每一个系统的数据层都会实现事务。数据层的事务都是继承自DBTransaction,派生自IDbTransaction的。下面介绍一下IDbTransaction的基本成员:

public interface IDbTransaction:IDisposable
{
      IDbConnection Connection {get;}     //返回Connection对象
      IsolationLevel IsolationLevel{get;}   
      void Commit();      //数据提交,把所有改变数据保存到持久化数据库
      void Rollback();     //数据回滚,把所有数据恢复原值
}

  其 中Connection属性是返回初始化此事务时所引用的连接对象的。

  Commit()方法应该在完成所有数据操作后才调用,调用该方法后,已经改变的数 据将会保存到持久化数据库当中。

  而Rollback()是出现错误时调用的,调用后数据将返回初始值。

  IsolationLevel是指定遇到其它并行事 务时的处理方式。

 

 

ADO.NET当中有多个子类都继续自DBTransaction,其中SqlTransaction是比较常用 的,SqlTransaction中还定义了一个Save()方法,这个方法允许开发人员把失败的事务回滚到上一个保存点而不回滚整个事务。而在 DataContext类里面,Transaction属性会返回DBTransaction对象。

2.开发实例

在传统的ADO.NET中使用事务,方法如下:

private static void Execute(string connectionString)
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        connection.Open();

        SqlCommand command = connection.CreateCommand();
        SqlTransaction transaction;

        //启动事务
        transaction = connection.BeginTransaction("SampleTransaction");

        //设定SqlCommand的事务和连接对象
        command.Connection = connection;
        command.Transaction = transaction;

        try
        {
            command.CommandText ="Insert into ......";
            command.ExecuteNonQuery();
        
            // 完成提交
            transaction.Commit();
            ......
        }
        catch (Exception ex)
        {
            //数据回滚
            transaction.Rollback();
            .....
        }
    }
}

DataContext中使用事务,方法极其相似,不同的是SqlCommand中事务为SqlTransaction,在DataContext中事务为DbTransaction

 using(MyDataContext context=new MyDataContext())
 {
      try
      {
          context.Connection.Open();
          context.Transaction=context.Connection.BeginTransaction();
          //更新数据
          .........
          context.SubmitChanges();
          //事务提交
          context.Transaction.Commit();
      }
      catch(Excetion ex)
      {
         //数据回滚
         context.Transaction.Rollback();
         //错误处理
         .........
      }
}

 

 

 

分布式隐式事务

嵌套式事务经常会出现在项目中,但往往容易被大家忽略,下面介绍一下 嵌套式事务的用法:(该事例可以说明很多问题)

使用分布式事务必须开启服务:Distributed Transaction Coordinator 否则报错  “服务器 'MRWANG' 上的 MSDTC 不可用 ”

//分布在不同数据库之间的事务操作
using(Connection con1=new Connection("连接字符串1"))
using (TransactionScope scope1 = new TransactionScope())            
 {
        //............scope1事务处理操作
        ///--此处提交将和外层(scope1)TransactionScope无关--
       //在无事务环境中执行操作,取消环境事务(TransactionScopeOption.Suppress)
            //每次进入创建新的事务环境(TransactionScopeOption.RequiresNew)          
         ///--scope2提交将和外层(scope1)TransactionScope有密切关联,scope2提交成功,外层遇到异常,则全部回滚,因为属于同一个事务环境--
                    //每次进入将检测是否存在事务环境,若存在则使用,不存在则重新创建(TransactionScopeOption.Required)
      using(Connection con2=new Connection("连接字符串2"))
        using (TransactionScope scope2=new TransactionScope(TransactionScopeOption.Required))
        {

        //............scope2事务处理操作
       scope2.Complete(); //只完成嵌套式的内部事务,但事务并未正式提交
        }
        scope1.Complete();  //代表完成所有事务,事务正式提交
 }

  

一 般项目中,大家都只会把事务用在DAL层,用于管理数据的CRUD,

但其实在一些操作中,某些数据的操作必须具有一致性

比如在订单管理中,当插入一条 OrderItem时,Order表内的总体价格,商品数量等也会随之改变。很多人把两个表的操作合成一个方法,放在OrderDAL中完成。但其实这样 做违返设计的原则,因为计算Order的总体价格时可能会包含商品优惠、客户等级、客户积分等等业务逻辑,而在DAL层不应该包含任何的业务逻辑存在的, 所以这样操作应该放在业务层完成

这时候,业务层的方法内就需要同时调用OrderItemDAL的AddOrderItem(OrderItem) 方法和OrderDAL的UpdateOrder(Order)方法,为了保证数据的一致性更新,就需要使用嵌套式事务

但这往往容易被开发人员所忽略, 当Order表的更新成功而OrderItem表的插入失败时,系统不能保证数据的同步回滚,那就会造成数据的逻辑性错误。

 

下面的例子就是为了保证数据一致性更新而使用的嵌套式事务在使用嵌套式事务的时候要应该注意及其把对象释放,避免造成死锁

namespace DAL
{     
     public class OrderDAL
     {
         public void UpdateOrder(Order order)
         {
             using (TransactionScope scope = new TransactionScope())
             {
                  ......         
                  scope.Complete();
             }
         }
     }
 
     public class OrderItemDAL
     {
         public void AddOrderItem(OrderItem orderItem)
         {
             using (TransactionScope scope = new TransactionScope())
             {
                 ......
                 scope.Complete();
             }
         }
     }
 }

namespace BLL
{
     public class OrderManager
     {
         public void AddOrderItem(OrderItem item)
         {
             using (TransactionScope scope = new TransactionScope())
             {
                 OrderItemDAL orderItemDAL=new OrderItemDAL();
                 orderItemDAL.AddOrderItem(item);
                 OrderDAL orderDAL=new OrderDAL();
                 ........
                 orderDAL.UpdateOrder(order);
                 scope.Complete();
             }
         }
     }
}

 

 

 

异步事务

事务类Transaction的方法中包含方法

public DependentTransaction DependentClone(DependentCloneOption)

此方法作用是克隆当前的事务,它在多线程调用同一事务的情况下使用经常使用。其中DependentCloneOption包含有两个选项:

一为BlockCommitUntilComplete,这表示在依赖事务未完成前,事务将处于阻塞状态,只有在所有依赖事务完成后,事务才能执行提交;

二为RollbackInNotComplete,这表示依赖事务必须在事务完成前调用Complete(),否则事务会被视为失败。

 

普通情况下,事务都会通过Transaction.Current 来获取,但此方法只能获取当前线程下的事务对象,在异步方法当中,这只会返回一个空值 null 。此时就需要使用DependentClone 方法获取依赖事务对象 DependentTransaction ,再把此对象作为参数传递到回调函数中。

     class Program
     {
         static void Main(string[] args)
         {
             Method();
             Console.ReadKey();
         }
 
         static void Method()
         {
             using (TransactionScope scope = new TransactionScope())
             {
                 ShowMessage("Main Thread");
 
                 //获取一个依赖事务,把依赖事务作为回调参数传到回调函数中
                 DependentTransaction dependentTransaction=
                    Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
                 ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncThread), dependentTransaction);
                 ........
                 scope.Complete(); //完成主线程事务,在依赖事务完成前,事务提交将处于阻塞状态
             }
         }
 
         static void AsyncThread(object transaction)
         {
             //获取依赖事务,利用TransactionScope(Transaction)构造函数生成隐式事务
             DependentTransaction dependentTransaction = (DependentTransaction)transaction;
             using (TransactionScope scope = new TransactionScope(dependentTransaction))
             {
                 ShowMessage("AsyncThread");
                 ..........
                 scope.Complete();  //完成异步事务
             }
             //完成依赖事务
             dependentTransaction.Complete();
         }
 
         static void ShowMessage(string data)
         {
             if (Transaction.Current != null)
             {
                 Transaction transaction = Transaction.Current;
                 string info = string.Format("{0}:{1}\nTransaction:\n  DistributedIndentifier:{2} \n  LocalIndentifier:{3}\n",
                     data,Thread.CurrentThread.ManagedThreadId.ToString(),
                     transaction.TransactionInformation.DistributedIdentifier,
                     transaction.TransactionInformation.LocalIdentifier);
                 Console.WriteLine(info);
             }
         }
     }

  

首 先在主线程中利用 Transaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete) 方法生成一个依赖事务,注意方法使用了BlockCommitUntilComplete的方式生成,即事务将在所有依赖事务使用Complete()后 才能执行提交。

然后利用ThreadPool.QueueUserWorkItem(WaitCallback,Object)方法把依赖事务作为回调参数传递到回调函数中

最后在回调函数中使用TransactionScope(transaction)构造函数生成对象,这代表把参数transaction作为当前的环境事务对象。观察下面的运行结果,两个线程中的事务都是同一个事务

 

 

 

结束语

事务是在多个层次都会使用到的,但很多项目当中往往会忽略了这一点而只在数据层使用,在大型的系统当中这样可能会影响到系统的一致性。特别是在分布式系统当中,操作往往同时存在于多个不同的系统当中,事务的处理更显示出其重要性。

 

 

 

一、事务定义:

  显事务:明确指定事务的开始,connection需要打开方可使用,默认为closed,即:显示调用con.BeginTransaction()

  隐式事务:无法明确指定事务的开始,默认connection已被打开为open 

  分布式隐式事务:使用TransactionScope类   , 【跨库且使用同一个事务提交】

  分布式显式事务:使用CommittableTransaction类,con.EnlistTransaction(Transaction对象)//将连接登记到事务 【跨库且使用同一个事务提交】

 

二、显示事务实例:

           using (DataClasses1DataContext datacontext = new DataClasses1DataContext())
            {
                try
                {
                    //必须打开连接,用于事务的创建
                    if (datacontext.Connection.State == ConnectionState.Closed) datacontext.Connection.Open();
                    //创建事务以及将创建的事务分配给该DataContext的Transaction
                    datacontext.Transaction = datacontext.Connection.BeginTransaction();
                    var test_tran = datacontext.Products.Where(a => a.ProductID < 5).OrderBy(a => a.ProductID).Select(a => a);
                    foreach (var test in test_tran)
                    {
                        test.ProductName = test.ProductName + "A";
                    }
                    //datacontext.SubmitChanges();
                    //提交事务
                    //datacontext.Transaction.Commit();
                }
                catch 
                {
                    //回滚事务
                    datacontext.Transaction.Rollback(); 
                }
            }        

 

 

三、隐式事务实例:

using (MyDataContext datacontext = new MyDataContext())
{
  TransactionOptions option=new TransactionOptions();
    option.IsolationLevel =System.Transactions.IsolationLevel.ReadCommitted;//隔离级别
      option.Timeout = new TimeSpan(0, 2, 0);//事务超时时间  为2分钟 默认为60秒
      using (TransactionScope scope = new TransactionScope())            
      {
    try
           {
       //在无事务环境中执行操作,即:取消环境事务(TransactionScopeOption.Suppress)
            //每次进入创建新的事务环境(TransactionScopeOption.RequiresNew)          
        //每次进入将检测是否存在事务环境,若存在则使用,不存在则重新创建(TransactionScopeOption.Required)
          var test_tran = datacontext.Products.Where(a => a.ProductID < 5).OrderBy(a => a.ProductID).Select(a => a);
                    foreach (var test in test_tran)
                    {
                        test.ProductName = test.ProductName + "A";
                    }
     datacontext.SubmitChanges();
        scope.Complete();  //代表完成事务,事务提交
   }catch{//遇到异常自动回滚}
}

  

三、分布式隐式事务实例:
   使用分布式事务必须开启服务:Distributed Transaction Coordinator 否则报错  “服务器 'MRWANG' 上的 MSDTC 不可用 ”

   分布在不同数据库之间的事务操作,如下事例:MyDataContext 和 OtherDataContext 两个不同数据库 即:分布式

 

using (MyDataContext datacontext1 = new MyDataContext())
{
  using (TransactionScope scope1 = new TransactionScope())            
 {
 try{     
     var test = datacontext1.TestTable.Where(a => a.ID == 1);
      foreach (var test in test)
       {
          test.Name = test.Name + "B";
       }
       datacontext1.SubmitChanges();
       注:
        ///////////--如下设置以后,嵌套事务(scope2)提交将和外层(scope1)TransactionScope无关--////////////
             //1.在无事务环境中执行操作,取消环境事务(TransactionScopeOption.Suppress)
                 //2.每次进入创建新的事务环境(TransactionScopeOption.RequiresNew)          
       ///////////////--scope2提交将和外层(scope1)TransactionScope有密切关联,scope2提交成功,外层遇到异常,则全部回滚,因为属于同一个事务环境--//////////////
               //1.每次进入将检测是否存在事务环境,若存在则使用,不存在则重新创建(TransactionScopeOption.Required)   
      using (OtherDataContext datacontext2 = new OtherDataContext())
          using (TransactionScope scope2=new TransactionScope(TransactionScopeOption.Required))
          {       
          var test_tran = datacontext2.Products.Where(a => a.ProductID < 5).OrderBy(a => a.ProductID).Select(a => a);
                    foreach (var test in test_tran)
                    {
                        test.ProductName = test.ProductName + "A";
                    }
               datacontext2.SubmitChanges();
             scope2.Complete(); //只完成嵌套式的内部事务,但事务并未正式提交
          }
      scope1.Complete();  //代表完成所有事务,事务正式提交
    }catch{//遇到异常自动回滚}
}

  

 

 

三、分布式显式事务实例:

using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MySqlServer"].ConnectionString))
{
    using (CommittableTransaction tran= new CommittableTransaction())
    {
        conn.Open();
        conn.EnlistTransaction(tran);//将连接登记到事务
        using (SqlCommand cmd = new SqlCommand())
        {
            cmd.Connection = conn;
            cmd.CommandType = CommandType.Text;
            try
            {
                cmd.CommandText = "insert into TranTable(Priority) values(1)";
                cmd.ExecuteNonQuery();
                cmd.CommandText = "insert into TranTable(Priority) values(256)";
                cmd.ExecuteNonQuery();
                tran.Commit();//提交事务
            }
            catch (SqlException ex)
            {
        //回滚
                tran.Rollback();
            }
        }
    }
    conn.Close();
}
 

  

 

 

一、TransactionScope和CommittableTransaction 构造函数其中重要参数: 

TransactionOptions option = new TransactionOptions();
/* --隔离级别
级别一 read uncommitted System.Transactions.IsolationLevel.ReadUncommitted --未提交读  (俗称“脏读”,能够读取其他用户正在修改的尚未提交的数据,无法确保数据的正确性)
级别二 read committed System.Transactions.IsolationLevel.ReadCommitted --提交读(无法读取正在修改【未提交的】的数据,即:读取修改后的数据)
级别三 repeatable read System.Transactions.IsolationLevel.RepeatableRead --可重复读(无法修改正在读取【未提交的】的数据,即:读取修改后的数据)
级别四 serializable System.Transactions.IsolationLevel.Serializable --可串行读  (最高隔离级别,一个事务未提交,另一个事务就会一直等待你提交数据)
*/
option.IsolationLevel =System.Transactions.IsolationLevel.ReadCommitted;//隔离级别
option.Timeout = new TimeSpan(0, 2, 0);//事务超时时间 为2分钟 默认为60秒
TransactionScope scope=new TransactionScope(TransactionScopeOption.Required,option)
CommittableTransaction comtran=new CommittableTransaction(option); 

  

 

延迟加载转换为即使加载

一、LoadWith的使用(对加载的数据没有限制)

    DataClasses1DataContext datacontext = new DataClasses1DataContext();
  //如果不设置为false,则LoadWith也没起到什么作用(无效),
    datacontext.DeferredLoadingEnabled = false;//关闭延迟加载,采用及时加载
    DataLoadOptions options = new DataLoadOptions();
  //如果关闭了延迟加载,也未采用LoadWith进行加载,则加载Products类的时候Categories=null和OrderDetails=null 没有值 ,因为未加载嘛
   //如果未关闭延迟加载,采用或者未采用LoadWith进行加载,则加载Products类的时候Categories和OrderDetails都有值,因为未关闭延迟加载,将加载所有数据 . 
      //这样在很多情况下是不建议这么做的,浪费资源,加载了也不用(有可能加载Products的时候我只需要Categories对象的值不需要OrderDetails的值)
  //Categories属性和OrderDetails属性来自于Products中的主外键关系的配置
    options.LoadWith<Products>(a => a.Categories);//加载Products类的同时也加载了关联属性Categories
    options.LoadWith<Products>(a => a.OrderDetails);//加载Products类的同时也加载了关联属性OrderDetails

   datacontext.LoadOptions = options;
   var categoriesload = datacontext.Products.Select(a => a).ToList();

  

总结:
(使用即使加载后,转换为多表的联合查询,只需要执行一次,就可以获取到关联对象属性的值,这样也有弊端,读取的是整张表的数据,这样返回的数据量比使用延迟加载时更大,而且可能返回不被使用的一些数据).

 

 

二、AssociateWith的使用(对加载的数据有一定的限制)

    DataClasses1DataContext datacontext = new DataClasses1DataContext();
  //如果不设置为false,则AssociateWith也没起到什么作用(无效),
    datacontext.DeferredLoadingEnabled = false;//关闭延迟加载,采用及时加载
    DataLoadOptions options = new DataLoadOptions();
  //如果关闭了延迟加载,也未采用LoadWith进行加载,则加载Products类的时候OrderDetails=null 没有值 ,因为未加载嘛
   //,就是用了LoadWith也未达到我们要的效果,因为获取的是OrderDetails中所有的数据,而我需要的不是这些,所以在使用AssociateWith的混合,来设置加载条件
    //如果未关闭延迟加载,采用或者未采用LoadWith进行加载,则加载Products类的时候OrderDetails,Categories等外键表都有值,因为未关闭延迟加载,将加载所有数据 . 
      //这样在很多情况下是不建议这么做的,浪费资源,加载了也不用(有可能加载Products的时候我只需要OrderDetails对象中某些数据)
  //OrderDetails属性来自于Products中的主外键关系的配置
  //--混合使用
  options.LoadWith<Products>(a=>a.OrderDetails);
  //加载主键表(Products)的所有数据的同时加载外键表(OrderDetails)ProductID=2的数据
  //Products是OrderDetails的外键表
   options.AssociateWith<Products>(a => a.OrderDetails.where(b=>b.ProductID==2));

   datacontext.LoadOptions = options;
   var categoriesload = datacontext.Products.Select(a => a).ToList();

  

总结:

(使用即使加载和AssociateWith后,转换为多表的联合查询,只需要执行一次,就可以获取到关联对象属性的某些数据,并且可以避免获取不必要的数据,减少了数据的加载量).
posted @ 2018-03-19 15:55  PanPan003  阅读(178)  评论(0编辑  收藏  举报