Ado.net事务

      我们可以用SqlConnection.BeginTransaction来使用事务,但我们也可以用TransactionScope来创建事务。SqlConnection.BeginTransaction大家都比较熟悉,这里主要说一下TransactionScope.

1、使用TransactionScope。

    TransactionScope事务,称为“本地量级事务”。如果需要会自动升级为完全分布式事务。该类创建一个范围,事务可在其中生存并自动提交或退回事务。代码如下:
using System.Transactions;
using System.Configuration;
using System.Data.SqlClient;
        /// <summary>
        
/// 使用TransactionScope事务
        
/// </summary>
        private void UseTransactionScope()
        {
            ConnectionStringSettings cnSetting 
= ConfigurationManager.ConnectionStrings["NorthWindDb"];
            TransactionOptions opt 
= new TransactionOptions();
            opt.IsolationLevel 
= System.Transactions.IsolationLevel.Serializable;

            
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required, opt))
            {
                
using (SqlConnection cn = new SqlConnection())
                {
                    cn.ConnectionString 
= cnSetting.ConnectionString;
                    cn.Open();
                    
using (SqlCommand cmd = cn.CreateCommand())
                    {
                        cmd.CommandText 
= "select count(*) from employees";
                        
int count = (int)cmd.ExecuteScalar();
                    }
                    ts.Complete();
                }
            }
        }
IsolationLevel有以下几种:
            Unspecified:正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。
            Chaos:无法覆盖隔离级别更高的事务中的挂起的更改。
            ReadUncommitted:可以进行脏读,意思是说,不发布共享锁,也不接受独占锁。
            ReadCommitted:在正在读取数据时保持共享锁,以避免脏读,但是在事务结束之前可以更改数据,从而导致不可重复的读取或幻像数据。
            RepeatableRead:在查询中使用的所有数据上放置锁,以防止其他用户更新这些数据。防止不可重复的读取,但是仍可以有幻像行。
            Serializable:在 DataSet 上放置范围锁,以防止在事务完成之前由其他用户更新行或向数据集中插入行。
            Snapshot:通过在一个应用程序正在修改数据时存储另一个应用程序可以读取的相同数据版本来减少阻止。表示您无法从一个事务中看到在其他事务中进行的更改,即便重新查询也是如此。

 TransactionScopeOption有几下几种:

            Required:该范围需要一个事务。如果已经存在环境事务,则使用该环境事务。否则,在进入范围之前创建新的事务。这是默认值。
            RequiresNew:总是为该范围创建新事务。
            Suppress:环境事务上下文在创建范围时被取消。范围中的所有操作都在无环境事务上下文的情况下完成。

 2、使用TransactionScope创建自己的事务资源管理器

    例如,我们有个Employee类,让此对象拥有事务功能。做法如下:

    1) 在项目中建立Employee类,让此类继承System.Transactions.ISinglePhaseNotification接口。并实现接口中的方法。同时在对某个属性进行修改时,要判断有没有正在执行的事务,如果没有,则立即修改值;否则将对象添加到事务中,并在提交事务之后更新提交值。代码如下。     

using System.Transactions;
    class Employee : IEnlistmentNotification
    {
        
/*
         * IEnlistmentNotification接口,建立自己的事务资源管理器。
         * 
         
*/
        
private Transaction currentTransaction;

        
private string workingEmployeeName;
        
private string committedEmployeeName;

        
public string EmployeeName
        {
            
get
            {
                
return workingEmployeeName;
            }
            
set
            {
                workingEmployeeName 
= value;
                
if (!Enlist())
                {
                    committedEmployeeName 
= value;
                }
            }
        }

        
public Employee(string employeeName)
        {
            EmployeeName 
= employeeName;
        }

        
public override string ToString()
        {
            
return string.Format("Employee WorkingVal {0} | CommittedVal {1}",
                workingEmployeeName, committedEmployeeName);
        }

        
/// <summary>
        
/// 确认方法
        
/// </summary>
        private void internalCommit()
        {
            committedEmployeeName 
= workingEmployeeName;
        }

        
/// <summary>
        
/// 退回方法
        
/// </summary>
        private void internalRollback()
        {
            workingEmployeeName 
= committedEmployeeName;
        }
        /// <summary>
        
/// 判断是否有事务
        
/// </summary>
       
public bool Enlist()
        {
            
if (currentTransaction != null)
            {
                
return true;
            }
            currentTransaction 
= Transaction.Current;
            
if (currentTransaction == null)
            {
                
return false;
            }
            
//登记阶段提交的可变资源管理器以参与事务
            currentTransaction.EnlistVolatile(this, EnlistmentOptions.None);
            
return true;
        }


        
#region 实现接口

        
/// <summary>
        
/// 在事务的第一个阶段中,可以在此询问是否执行事务
        
/// </summary>
        
/// <param name="preparingEnlistment"></param>
        public void Prepare(PreparingEnlistment preparingEnlistment)
        {
            System.Diagnostics.Debug.WriteLine(
"准备:" + this.ToString());
            preparingEnlistment.Prepared();
        }

        
/// <summary>
        
/// 事务的第二个阶段。可以在此通即将执行事务。成功提交事务后,将执行该对象的Done方法。
        
/// </summary>
        
/// <param name="enlistment"></param>
        public void Commit(Enlistment enlistment)
        {
            System.Diagnostics.Debug.WriteLine(
"确认前为:" + this.ToString());
            internalCommit();
            currentTransaction 
= null;
            enlistment.Done();
        }
        
/// <summary>
        
/// 通知登记的对象事务的状态不确定。
        
/// </summary>
        
/// <param name="enlistment"></param>
        public void InDoubt(Enlistment enlistment)
        {
            System.Diagnostics.Debug.WriteLine(
"InDoubt:" + this.ToString());
            currentTransaction 
= null;
            
throw new TransactionAbortedException("Commit results cannot be determined");
        }

        
/// <summary>
        
/// 在退回事务时调用此方法。
        
/// </summary>
        
/// <param name="enlistment"></param>
        public void Rollback(Enlistment enlistment)
        {
            System.Diagnostics.Debug.WriteLine(
"退回前:" + this.ToString());
            currentTransaction 
= null;
            
this.internalRollback();          
        }
        
#endregion

    }

    2)测试事务

    编写下边代码,测试Employee的事务功能,我们可以在Output窗口中看到详细的执行情况。

        /// <summary>
        
/// 测试事务
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="e"></param>
        private void btnUserdefined_Click(object sender, EventArgs e)
        {
            Employee e1 
= new Employee("用户1");
            Employee e2 
= new Employee("用户2");
            
try
            {
                
using (TransactionScope ts = new TransactionScope())
                {
                    e1.EmployeeName 
= "用户1改写值";
                    e2.EmployeeName 
= "用户2改写值";
                    
////测试Rollback
                    //throw new Exception();
                    ts.Complete();
                }
            }
            
catch (Exception xcp)
            {
                System.Diagnostics.Debug.WriteLine(
"Exception:" + xcp.Message);
            }
            System.Diagnostics.Debug.WriteLine(
"最终e1为: " + e1.ToString());
            System.Diagnostics.Debug.WriteLine(
"最终e2为: " + e2.ToString());
        }

   上边代码中有两个资源需要提交,即(e1.EmployeeName = "用户1改写值";  e2.EmployeeName = "用户2改写值";)所以TransactionScope会自动升级成分布式事务。当我们只有一个资源需要提交时,如注释掉e2。这时可以实现ISinglePhaseNotification接口,实现快速提交。

3、使用ISinglePhaseNotification接口实现快速提交

   1)只需要实现ISinglePhaseNotification接口中的SinglePhaseCommit方法即可。

using System.Transactions;

namespace TransactionUse
{
    
class Employee : IEnlistmentNotification, ISinglePhaseNotification
    {
        
/*
         * IEnlistmentNotification接口,建立自己的事务资源管理器。
         
*/
         ……之前的代码       

        
#region ISinglePhaseNotification接口实现
        
/*
         在只需要提效一个资源时,使用此接口快速提交资源。
         *注释掉第二个Employee e2。窗口只显示提交阶段的结果,而不会显示准备阶段的执行结果。
         
*/
        
public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)
        {
            System.Diagnostics.Debug.WriteLine(
"快速提交资源前:" + this.ToString());
            internalCommit();
            currentTransaction 
= null;
            
//指示事务参与者已完成其工作。
            singlePhaseEnlistment.Done();
        }
        
#endregion

    }
}

   2)测试
    在上边的btnUserdefined_Click事件中,将e2注释掉,运行程序测试。可以看到执行的情况。此时只会显示执行阶段的执行结果,而不会显示准备阶段的执行结果。

 源码下载:TransactionUse.rar(Vs2010)

原文地址:https://www.cnblogs.com/scottckt/p/1963709.html