Understanding TransactionScope Procedural Activities in WF 4.0 - An Application Development Approach.

Posted by: Mahesh Sabnis , on 8/29/2009, in Category .NET Framework
Views: 15775
Abstract: In this article we will explore and understand TransactionScope Procedural Activities in Windows Workflow (WF) 4.0.
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:
AddNewItem
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:
DataTransaction
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:
Arguments
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:
Variables
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:

Parameters
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:
Parameters_
Now the sequence activity inside, the TransactionScope activity will be as below:
DmlSequence
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:
Form1
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’.
DTC
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
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+
Further Reading - Articles You May Like!
Author
Mahesh Sabnis is a DotNetCurry author and Microsoft MVP having over 17 years 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). Follow him on twitter @maheshdotnet


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!

Categories

JOIN OUR COMMUNITY

POPULAR ARTICLES

FREE .NET MAGAZINES

Free DNC .NET Magazine

Tags

JQUERY COOKBOOK

jQuery CookBook