此示例演示客户端协调事务的用法,以及为了控制服务事务行为而对 ServiceBehaviorAttribute 和 OperationBehaviorAttribute 进行的设置。持久写入服务器日志表依赖于客户端协调事务的结果 - 如果客户端事务未完成,Web 服务事务可以确保不会提交对数据库的更新。
下面我们把程序的代码依次列出来。
契约和服务类的代码:
using System;
using System.Configuration;
using System.Data.SqlClient;
using System.Globalization;
using System.ServiceModel;
using System.Transactions; namespace Microsoft.ServiceModel.Samples
{
// Define a service contract.
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode = SessionMode.Required)]
public interface ICalculator
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
double Add(double n);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
}
// Service class which implements the service contract.
[ServiceBehavior(
TransactionIsolationLevel=IsolationLevel.Serializable,
TransactionTimeout="00:00:30",
ReleaseServiceInstanceOnTransactionComplete=false,
TransactionAutoCompleteOnSessionClose=false)]
public class CalculatorService : ICalculator
{
double runningTotal;
public CalculatorService()
{
Console.WriteLine("Creating new service instance ");
}
[OperationBehavior(TransactionScopeRequired
=true,TransactionAutoComplete=true)]public double Add(double n)
{
RecordLog(String.Format(CultureInfo.CurrentCulture,"Adding {0} to {1}",n,runningTotal));
runningTotal = runningTotal + n;
return runningTotal;
}
[OperationBehavior(TransactionScopeRequired
=true,TransactionAutoComplete=true)]public double Subtract(double n)
{
RecordLog(String.Format(CultureInfo.CurrentCulture, "Subtracting {0} from {1}", n, runningTotal));
runningTotal = runningTotal - n;
return runningTotal;
}
[OperationBehavior(TransactionScopeRequired
=true,TransactionAutoComplete=false)]public double Multiply(double n)
{
RecordLog(String.Format(CultureInfo.CurrentCulture, "Multiplying {0} by {1}", n, runningTotal));
runningTotal = runningTotal * n;
return runningTotal;
}
[OperationBehavior(TransactionScopeRequired
=true,TransactionAutoComplete=false)]public double Divide(double n)
{
RecordLog(String.Format(CultureInfo.CurrentCulture,"Dividing {0} by {1}",n,runningTotal));
runningTotal = runningTotal / n;
OperationContext.Current.SetTransactionComplete();
return runningTotal;
}
public static void RecordLog(string recordText)
{
// Record the operations performed
if (ConfigurationManager.AppSettings["usingSql"] == "true")
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager.AppSettings["connectingString"]))
{
conn.Open();
SqlCommand cmdLog
= new SqlCommand("INSERT into Log(Entry,AddTime) VALUES (@Entry,@AddTime)", conn);cmdLog.Parameters.AddWithValue("@Entry", recordText);
cmdLog.Parameters.AddWithValue("@AddTime", DateTime.Now.ToString());
cmdLog.ExecuteNonQuery();
cmdLog.Dispose();
SqlCommand cmdCount
= new SqlCommand("SELECT COUNT(*) FROM Log", conn);string rowCount = cmdCount.ExecuteScalar().ToString();
cmdCount.Dispose();
Console.WriteLine(
"Writing row {0} to database : {1}", rowCount, recordText);conn.Close();
}
}
else
{
Console.WriteLine("Nothing row : {0}",recordText);
}
}
}
}
服务端的配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="usingSql" value="true"/>
<add key="connectingString" value="Data Source=.\SQLExpress;AttachDbFilename=|DataDirectory|\SampleDB.mdf;Integrated Security=True;User Instance=True"/>
</appSettings>
<system.serviceModel>
<services>
<service name="Microsoft.ServiceModel.Samples.CalculatorService" behaviorConfiguration="CalculatorServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000/ServiceModelSamples/service"/>
</baseAddresses>
</host>
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="TransactionalBinding" contract="Microsoft.ServiceModel.Samples.ICalculator"></endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"></endpoint>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="TransactionalBinding" transactionFlow="true"></binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="CalculatorServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
客户端的代码:
using System;
using System.Transactions; namespace Microsoft.ServiceModel.Samples
{
class Program
{
static void Main(string[] args)
{
// Create a client
CalculatorClient client = new CalculatorClient();
// Create a transaction scope with the default isolation level of Serializable
using (TransactionScope tx = new TransactionScope())
{
Console.WriteLine("Starting transaction");
// Call the Add service operation.
double value = 100.00D;
Console.WriteLine(" Adding {0}, running total={1}",
value, client.Add(value));
// Call the Subtract service operation.
value = 45.00D;
Console.WriteLine(" Subtracting {0}, running total={1}",
value, client.Subtract(value));
//throw new Exception();
// Call the Multiply service operation.
value = 9.00D;
Console.WriteLine(" Multiplying by {0}, running total={1}",
value, client.Multiply(value));
// Call the Divide service operation.
value = 15.00D;
Console.WriteLine(" Dividing by {0}, running total={1}",
value, client.Divide(value));
Console.WriteLine(
" Completing transaction");tx.Complete();
}
Console.WriteLine(
"Transaction committed");// Closing the client gracefully closes the connection and cleans up resources
client.Close();
Console.WriteLine(
"Press <ENTER> to terminate client.");Console.ReadLine();
}
}
}
正常情况下,我们运行客户端,会发现数据表中添加了ID从1到4的4条记录。
1. 注释掉tx.Complete(),我们重新编译客户端并运行,会发现服务端和客户端的控制台正常显示,但数据库中并未添加数据。
这时,我们取消对tx.Complete()的注释,重新编译运行后,会发现Log表中新纪录数据的开始的ID为9。缺少的ID为5~8的4条数据即为客户端在未完成其事务的情况下退出事务范围所造成的数据回滚。
2. 在客户端注释掉对Divide()方法的调用,重新编译客户端并运行,程序运行到using (TransactionScope tx = new TransactionScope())的结束符出现异常“已中止事务”。这时,我们取消对Divide()方法的注释,重新编译运行后,查看数据表,我们会发现新数据开始的ID为16。缺少了ID为13~15这3条数据,这是由于Multiply操作启动的事务无法完成,从而使客户端事务最终也无法完成,造成数据回滚。
3. 我们在调用减法后添加一个抛出异常的语句:throw new Exception();编译并运行客户端,程序运行完减法后即抛出异常。这时,将这条抛出异常的语句注释掉,重新编译运行客户端程序,我们会看到数据表中添加的新数据的ID是从22开始的。缺少的ID为20、21,这是由于异常阻止了客户端完成其事务所造成的数据回滚。
下面我把通过以上操作数据表上添加的记录贴出来,