Understanding TransactionScope Procedural Activities in WF 4.0 - An Application Development Approach
In this article we will explore and understand TransactionScope Procedural Activities in Windows Workflow (WF) 4.0. I hope most of you have read my previous articles using .NET 4.0 and WF 4.0. Those who have already used WF 3.x must have used the TransactionScope activity. As part of .NET 2.0, we were provided with a new class 'TransactionScope'. The use of this class is to keep track of the distributed transaction across multiple stores. For this purpose, this class makes use of Distributed Transaction Coordinator (DTC) service provided as a part of NT based OS like XP, VISTA, and Win2K3 etc.
Consider a scenario where you are developing an application for medical purposes. The details are as follows:- For a patient, at the time of discharge, a bill needs to be generated. But at the same time for that patient, the hospital wants to make an insurance claim against the bill to the insurance company. Now the insurance company has provided an interface for your application, and you want that the Workflow based patient billing system should automatically call the method from the interface. Your application is using SQL 2005 database ‘Medical’. Similarly the insurance company also uses a sSQL 2005 database ‘Insurance’. The hospital demands that the bill information and claim information should be entered at the same time and the data must also to be stored in both the database at that point of time.
Here in this case the application developed by you is using two separate connections to separate databases. The question arises - how does your application handle transactions in this distributed scenario. Well TransactionScope is the class, which provides you this facility. In the example shown below, we will see an application of this class.
Step 1: Open VS2010, create a new project, name this as ‘WF_TransactionScope’. By default you will get Sequence.Xaml. Right click on it and delete it.
Step 2: Right click on the project and add a new Sequence activity, name this as ‘WF_TransactionScopeSequence.xaml’ as below:
Step 3: In the workflow project add a class, name this class as ‘clsDML’. The code is as shown below:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
namespace WF_TransactionScope
{
public class clsDML
{
SqlConnection Conn_Bill, Conn_Insurance;
SqlCommand Cmd_Bill, Cmd_Insurance;
public void GenerateBill(int billNo, int patientId, string patientName, int billAmount)
{
Conn_Bill = new SqlConnection("Data Source=.;Initial Catalog=Medical;Integrated Security=SSPI");
Conn_Bill.Open();
Cmd_Bill = new SqlCommand();
Cmd_Bill.Connection = Conn_Bill;
Cmd_Bill.CommandText = "Insert into BillMaster Values (@BillNo,@PatientId,@PatientName,@BillAmount)";
Cmd_Bill.Parameters.AddWithValue("@BillNo", billNo);
Cmd_Bill.Parameters.AddWithValue("@PatientId", patientId);
Cmd_Bill.Parameters.AddWithValue("@PatientName", patientName);
Cmd_Bill.Parameters.AddWithValue("@BillAmount", billAmount);
Cmd_Bill.ExecuteNonQuery();
Conn_Bill.Close();
}
public void SubmitClaim(int claimId,string patientName,int policyNo,int policyAmount,int claimAmount)
{
Conn_Insurance = new SqlConnection("Data Source=.;Initial Catalog=Insurance;Integrated Security=SSPI");
Conn_Insurance.Open();
Cmd_Insurance = new SqlCommand();
Cmd_Insurance.Connection = Conn_Insurance;
Cmd_Insurance.CommandText = "Insert into ClaimMaster Values (@ClaimId,@PatientName,@PolicyNo,@PolicyAmount,@ClaimAmount)";
Cmd_Insurance.Parameters.AddWithValue("@ClaimId",claimId);
Cmd_Insurance.Parameters.AddWithValue("@PatientName", patientName);
Cmd_Insurance.Parameters.AddWithValue("@PolicyNo", policyNo);
Cmd_Insurance.Parameters.AddWithValue("@PolicyAmount", policyAmount);
Cmd_Insurance.Parameters.AddWithValue("@ClaimAmount", claimAmount);
Cmd_Insurance.ExecuteNonQuery();
Conn_Insurance.Close();
}
}
}
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Data.SqlClient
Namespace WF_TransactionScope
Public Class clsDML
Private Conn_Bill, Conn_Insurance As SqlConnection
Private Cmd_Bill, Cmd_Insurance As SqlCommand
Public Sub GenerateBill(ByVal billNo As Integer, ByVal patientId As Integer, ByVal patientName As String, ByVal billAmount As Integer)
Conn_Bill = New SqlConnection("Data Source=.;Initial Catalog=Medical;Integrated Security=SSPI")
Conn_Bill.Open()
Cmd_Bill = New SqlCommand()
Cmd_Bill.Connection = Conn_Bill
Cmd_Bill.CommandText = "Insert into BillMaster Values (@BillNo,@PatientId,@PatientName,@BillAmount)"
Cmd_Bill.Parameters.AddWithValue("@BillNo", billNo)
Cmd_Bill.Parameters.AddWithValue("@PatientId", patientId)
Cmd_Bill.Parameters.AddWithValue("@PatientName", patientName)
Cmd_Bill.Parameters.AddWithValue("@BillAmount", billAmount)
Cmd_Bill.ExecuteNonQuery()
Conn_Bill.Close()
End Sub
Public Sub SubmitClaim(ByVal claimId As Integer, ByVal patientName As String, ByVal policyNo As Integer, ByVal policyAmount As Integer, ByVal claimAmount As Integer)
Conn_Insurance = New SqlConnection("Data Source=.;Initial Catalog=Insurance;Integrated Security=SSPI")
Conn_Insurance.Open()
Cmd_Insurance = New SqlCommand()
Cmd_Insurance.Connection = Conn_Insurance
Cmd_Insurance.CommandText = "Insert into ClaimMaster Values (@ClaimId,@PatientName,@PolicyNo,@PolicyAmount,@ClaimAmount)"
Cmd_Insurance.Parameters.AddWithValue("@ClaimId",claimId)
Cmd_Insurance.Parameters.AddWithValue("@PatientName", patientName)
Cmd_Insurance.Parameters.AddWithValue("@PolicyNo", policyNo)
Cmd_Insurance.Parameters.AddWithValue("@PolicyAmount", policyAmount)
Cmd_Insurance.Parameters.AddWithValue("@ClaimAmount", claimAmount)
Cmd_Insurance.ExecuteNonQuery()
Conn_Insurance.Close()
End Sub
End Class
End Namespace
The above class has two methods, GenerateBill() and SubmitClaim(). These classes are establishing connection to database ‘Medical’ and ‘Insurance’ respectively.
Step 4: On the workflow, Drag-Drop ‘TransactionScopeActivity’. Set the display name of it as ‘Data Transaction’. By default this activity is in collapsed mode. Double click it and you will get the view as below:
In the body of the activity, only one activity can be placed.
Step 5: In the workflow designer, select ‘Arguments’ button (lower left) and declare following arguments. Make sure that, name of the argument matches with parameter name passed to the method. These are used as input arguments to the methods from the class:
Also to call methods from the class, we must declare an object of the class. Click ‘Variables’ button just beside the ‘Arguments’ button and declare variable as shown below:
Step 6: In the body, drag-drop a sequence activity and set its display name to ‘DML Sequence’. This is the container activity. This will now contain two ‘InvokeMethod’ activities. Drag-Drop two invoke method activities and set the properties as shown below:
InvokeActivity 1:
Property
|
Value
|
Display Name
|
Invoke Generate Bill
|
Method Name
|
GenerateBill
|
TargetObject
|
objDML
|
Parameters to these methods are declared using ‘Parameters’ property of the ‘Invoke Generate Bill’ invoke method activity is as shown below:
Expression here represents, arguments declared in the workflow.
InvokeMethod 2:
Property
|
Value
|
Display Name
|
Invoke Submit Claim
|
Method Name
|
SubmitClaim
|
TargetObject
|
objDML
|
Parameters to these methods are declared using ‘Parameters’ property of the ‘Invoke Sumbit Claim’ invoke method activity as shown below:
Now the sequence activity inside, the TransactionScope activity will be as below:
Step 7: Build the project, make sure that it compiles without any errors.
Step 8: In the solution right click and add a new winform project > name this as ‘WinForm_WFTransactionClient’.
Step 9: Design the WinForm as shown below:
Step 10: In the Window project add the references to the namespaces given below:
· WF_TransactionScope:- The name of the workflow project.
· System.Activities.
· System.WorkflowServices.
Step 11: At the Form class level declare the following objects:
C#
AutoResetEvent waitHandle; //refer: System.Threading.
WorkflowInstance instance; //Refer: System.Activities.
VB.NET
Dim waitHandle As AutoResetEvent 'refer: System.Threading.
Dim instance As WorkflowInstance 'Refer: System.Activities.
In the form load event write the following code:
C#
private void Form1_Load(object sender, EventArgs e)
{
waitHandle = new AutoResetEvent(false);
}
VB.NET
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs)
waitHandle = New AutoResetEvent(False)
End Sub
Write the following code for data entry in text boxes:
C#
private void txtpatname_TextChanged(object sender, EventArgs e)
{
txtpatname_1.Text = txtpatname.Text;
}
private void txtbillamt_TextChanged(object sender, EventArgs e)
{
txtclaimamt.Text = txtbillamt.Text;
}
VB.NET
Private Sub txtpatname_TextChanged(ByVal sender As Object, ByVal e As EventArgs)
txtpatname_1.Text = txtpatname.Text
End Sub
Private Sub txtbillamt_TextChanged(ByVal sender As Object, ByVal e As EventArgs)
txtclaimamt.Text = txtbillamt.Text
End Sub
In the button click, write the following code. This will create the dictionary object for passing parameters to Workflow. WorkflowInstance object starts the workflow and passes parameters to it.
C#
private void btnSaveDetails_Click(object sender, EventArgs e)
{
Dictionary<string, object> Par = new Dictionary<string, object>();
Par.Add("billNo",Convert.ToInt32(txtbillno.Text) );
Par.Add("patientId", Convert.ToInt32(txtpatid.Text));
Par.Add("patientName",txtpatname.Text );
Par.Add("billAmount",Convert.ToInt32(txtbillamt.Text ) );
Par.Add("claimId", Convert.ToInt32(txtclaimid.Text));
Par.Add("policyNo", Convert.ToInt32(txtpolictno.Text));
Par.Add("policyAmount", Convert.ToInt32(txtpolicyamt.Text));
Par.Add("claimAmount", Convert.ToInt32(txtclaimamt.Text));
instance = new WorkflowInstance(new WF_TransactionScope.WF_TransactionScopeSequence(),Par);
instance.OnCompleted = delegate(WorkflowCompletedEventArgs e1) { waitHandle.Set(); };
instance.OnUnhandledException = delegate(WorkflowUnhandledExceptionEventArgs e2)
{
MessageBox.Show(e2.UnhandledException.ToString());
return UnhandledExceptionAction.Terminate;
};
instance.OnAborted = delegate(WorkflowAbortedEventArgs e3)
{
MessageBox.Show(e3.Reason.ToString());
waitHandle.Set();
};
instance.Run();
waitHandle.WaitOne();
}
VB.NET
Private Sub btnSaveDetails_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim Par As New Dictionary(Of String, Object)()
Par.Add("billNo",Convert.ToInt32(txtbillno.Text))
Par.Add("patientId", Convert.ToInt32(txtpatid.Text))
Par.Add("patientName",txtpatname.Text)
Par.Add("billAmount",Convert.ToInt32(txtbillamt.Text))
Par.Add("claimId", Convert.ToInt32(txtclaimid.Text))
Par.Add("policyNo", Convert.ToInt32(txtpolictno.Text))
Par.Add("policyAmount", Convert.ToInt32(txtpolicyamt.Text))
Par.Add("claimAmount", Convert.ToInt32(txtclaimamt.Text))
instance = New WorkflowInstance(New WF_TransactionScope.WF_TransactionScopeSequence(),Par)
instance.OnCompleted = Function(e1) AnonymousMethod1(e1)
instance.OnUnhandledException = Function(e2) AnonymousMethod2(e2)
instance.OnAborted = Function(e3) AnonymousMethod3(e3)
instance.Run()
waitHandle.WaitOne()
End Sub
Private Function AnonymousMethod1(ByVal e1 As WorkflowCompletedEventArgs) As Boolean
waitHandle.Set()
Return True
End Function
Private Function AnonymousMethod2(ByVal e2 As WorkflowUnhandledExceptionEventArgs) As Object
MessageBox.Show(e2.UnhandledException.ToString())
Return UnhandledExceptionAction.Terminate
End Function
Private Function AnonymousMethod3(ByVal e3 As WorkflowAbortedEventArgs) As Boolean
MessageBox.Show(e3.Reason.ToString())
waitHandle.Set()
Return True
End Function
Step 12: Run the application, enter values in textbox and click on the button. Open Microsoft Management Console (MMC) and view DTC. If the values are successfully inserted, statistics will show ‘Committed’ Transactions and if any exception occurs, then the statistics will show ‘Aborted’.
Conclusion: TransactionScope activity is a professional use activity that helps to manage multiple transactions through workflow. If you are developing Workflow as a backbone of your distributed application architecture, then by using this activity, the additional code for handling transactions can be reduced.
The entire source code of this article can be downloaded over here
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Mahesh Sabnis is a DotNetCurry author and a Microsoft MVP having over two decades of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions), and Front-end technologies like Angular and React. Follow him on twitter @
maheshdotnet or connect with him on
LinkedIn