WCF 第五章 行为 在WCF一个服务内部的事务操作

事务化的服务操作只能作为一个整体成功或失败。它们以一个整体被初始化,假设结果将会是 一致的,无论操作最终是成功还是失败。图片5.9 使用伪代码描述这个行为。客户端打开一个到服务端的连接然后调用它的Transfer 方法。Transfer 执行一个借入,一个存入,然后标记事务完成。客户端在事务语义中不涉及。

12-9-2010 4-51-57 PM

  为了在WCF中实现这个行为,服务操作必须使用 [OperationBehavior(TransactionScopedRequired=true)]属性来标记为是可事务化的。这指导WCF创建 一个新的事务并在将控制权给那个方法前把执行线程入列。如果操作在它完成前失败了,所有在事务中进行的对事务资源的部分更新都将被回滚。

  如果TransactionScopedRequired=false 被设置(默认设置),操作就会在非事务下执行。在那个情况,操作将不支持ACID 属性。如果操作更新了一个表然后在更新第二个表时失败了,那么第一个表的更新将会保持,也就意味着ACID属性被破坏了。

  你可以指示一个操作通过隐式或显式方式完成。通过使用 [OperationBehavior(TransactionAutoComplete=false)]行为并在方法返回前显式调用 OperationContext.Current.SetTransactionComplete()。如果你使用显式方法,你也需要在通信信道中使用 一个基于会话的绑定元素,同时你需要在服务契约中使用 [ServiceContract(SessionMode=SessionMode.Allowed)]来支持会话。

  列表5.15 显示了一个服务,BankService,它暴露了两个服务操作。第一个服务操作,GetBalance,不是事务化的。它从数据库中读取数据并返回结 果。OpeartionBehavior 的TransactionScopedRequired=false 用来说明它不需要一个事务。第二个操作,Transfer,是事务化的而且在操作行为上添加了 TransactionScopedRequired=true.它调用两个内部方法,Withdraw和Deposit,每一个方法都通过 DBAccess来更新数据库。传输操作隐含使用TransactionAutoComplete=true属性来标记事务的完成。如果Withdraw 或者Deposit都没有抛出异常,两个操作的改变都被标记为完成。

  BankService 服务使用内部类DBAccess来访问所有数据库。注意它的构造函数打开一个到数据库的连接。当DBAccess超出范围而且没有请求或者事务是活跃的, 垃圾回收器将会关闭连接。主动尝试在一个析构函数中关闭连接将导致一个错误因为当超出类的范围时事务仍然可能是活跃的。

列表5.15 事务化操作

001namespace Services
002{
003    [ServiceContract(SessionMode=SessionMode.Required)]
004    public interface IBankService
005    {
006        [OperationContract]
007        double GetBalance(string accountName);
008 
009        [OperationContract]
010        void Transfer(string from, string to, double amount);
011    }
012    public class BankService : IBankService
013    {
014        [OperationBehavior(TransactionScopeRequired = false)]
015        public double GetBalance(string accountName)
016        {
017            DBAccess dbAccess = new DBAccess();
018            double amount = dbAccess.GetBalance(accountName);
019            dbAccess.Audit(accountName, "Query", amount);
020            return amount;
021        }
022        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete=true)]
023        public void Transfer(string from, string to, double amount)
024        {
025            try
026            {
027                Withdraw(from, amount);
028                Deposit(to, amount);
029            }
030            catch(Exception ex)
031            {
032                throw ex;
033            }
034        }
035        [OperationBehavior(TransactionAutoComplete = false, TransactionScopeRequired = true)]
036        [TransactionFlow(TransactionFlowOption.Allowed)]
037        private void Withdraw(string accountName, double amount)
038        {
039            DBAccess dbAccess = new DBAccess();
040            dbAccess.Withdraw(accountName, amount);
041            dbAccess.Audit(accountName, "Withdraw", amount);
042        }
043        [OperationBehavior(TransactionAutoComplete=false, TransactionScopeRequired=true)]
044        private void Deposit(string accountName, double amount)
045        {
046            DBAccess dbAccess = new DBAccess();
047            dbAccess.Withdraw(accountName, amount);
048            dbAccess.Audit(accountName, "Deposit", amount);
049        }
050    }
051 
052    class DBAccess
053    {
054        private SqlConnection conn;
055        public DBAccess()
056        {
057            string cs = ConfigurationManager.ConnectionStrings["sampleDB"].ConnectionString;
058            conn = new SqlConnection(cs);
059            conn.Open();
060        }
061        public void Deposit(string accountName, double amount)
062        {
063            string sql = string.Format("Deposit {0}, {1}", accountName, amount);
064            SqlCommand cmd = new SqlCommand(sql, conn);
065            cmd.ExecuteNonQuery();
066        }
067        public void Withdraw(string accountName, double amount)
068        {
069            string sql = string.Format("Withdraw {0}, {1}", accountName, amount);
070            SqlCommand cmd = new SqlCommand(sql, conn);
071            cmd.ExecuteNonQuery();
072        }
073        public double GetBalance(string accountName)
074        {
075            SqlParameter[] paras = new SqlParameter[2];
076            paras[0] = new SqlParameter("@accountName", accountName);
077            paras[1] = new SqlParameter("@sum", System.Data.SqlDbType.Float);
078            paras[1].Direction = System.Data.ParameterDirection.Output;
079 
080            SqlCommand cmd = new SqlCommand();
081            cmd.Connection = conn;
082            cmd.CommandType = System.Data.CommandType.StoredProcedure;
083            cmd.CommandText = "GetBalance";
084 
085            for (int i = 0; i < paras.Length; i++)
086            {
087                cmd.Parameters.Add(paras[i]);
088            }
089 
090            int n = cmd.ExecuteNonQuery();
091            object o = cmd.Parameters["@sum"].Value;
092            return Convert.ToDouble(o);
093        }
094        public void Audit(string accountName, string action, double amount)
095        {
096            Transaction txn = Transaction.Current;
097            if (txn != null)
098            {
099                Console.WriteLine("{0} | {1} Audit:{2}",
100                    txn.TransactionInformation.DistributedIdentifier,
101                    txn.TransactionInformation.LocalIdentifier, action);
102            }
103            else
104            {
105                Console.WriteLine("<no transaction> Audit:{0}", action);
106            }
107            string sql = string.Format("Audit {0}, {1}, {2}",
108                accountName, action, amount);
109            SqlCommand cmd = new SqlCommand(sql, conn);
110            cmd.ExecuteNonQuery();
111        }
112    }
113}

  这个例子的客户端代码在列表5.16中显示。服务端的事务对客户端是透明的。

列表5.16 客户端调用一个事务化服务

01BankServiceClient proxy = new BankServiceClient();
02Console.WriteLine("{0}: Before - savings:{1}, checking {2}",
03    DateTime.Now,
04    proxy.GetBalance("savings"),
05    proxy.GetBalance("checking"));
06proxy.Transfer("savings", "checking", 100);
07 
08Console.WriteLine("{0}: After - savings:{1}, checking {2}",
09    DateTime.Now,
10    proxy.GetBalance("savings"),
11    proxy.GetBalance("checking"));
12proxy.Close();

  因为两个内部方法,Withdraw 和Deposit,每个都创建了一个新的DBAccess类,它们每个都独立的打开一个到数据库的连接。当Withdraw在事务中打开第一个连接时,事务是一个本地事务的一部分而不是一个分布式事务的一部分。当它打开第二个连接时,事务扩大到一个分布式事务所以工作可以在两个连接之间合作。DBAccess.Audit 方法打印出事务的LocalIdentifier和DistributedIdentifier, 在图片5.10中显示。注意Withdraw没有在一个分布式事务中执行,因为它是在那个时间里唯一一个打开的连接。当Deposit执行时,它创建了一 个分布式事务因为它是在那个事务范围内第二个打开的连接。逐步扩大自动发生同时有一个很明显的对性能的不利影响。

  列表5.17 显示了完整的代码,当传输操作通过DBAccess打开一个连接并把这个连接同时发送给Withdraw和Deposit以便于只有一个连接被使用。

12-9-2010 5-34-35 PM

列表5.17 避免分布式事务的事务操作的完整代码

01[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete=true)]
02public void Transfer(string from, string to, double amount)
03{
04    try
05    {
06        DBAccess dbAccess = new DBAccess();
07        Withdraw(from, amount, dbAccess);
08        Deposit(to, amount, dbAccess);
09    }
10    catch(Exception ex)
11    {
12        throw ex;
13    }
14}
15[OperationBehavior(TransactionAutoComplete = false, TransactionScopeRequired = true)]
16[TransactionFlow(TransactionFlowOption.Allowed)]
17private void Withdraw(string accountName, double amount, DBAccess dbAccess)
18{
19    dbAccess.Withdraw(accountName, amount);
20    dbAccess.Audit(accountName, "Withdraw", amount);
21}
22[OperationBehavior(TransactionAutoComplete=false, TransactionScopeRequired=true)]
23private void Deposit(string accountName, double amount, DBAccess dbAccess)
24{
25    dbAccess.Withdraw(accountName, amount);
26    dbAccess.Audit(accountName, "Deposit", amount);
27}

  图片5.11显示了完整服务的输出结果。注意分布式事务ID总是为0,意味着不存在分布式事务。

12-9-2010 5-37-32 PM


======

转载自

 

posted @ 2011-06-30 09:53  Gavin Liu  阅读(254)  评论(0编辑  收藏  举报

Right people get the right information at the right time.
以技术求生存,以市场求发展;学以至用,开拓创新;达技术之颠峰,至市场之广阔!