Windows Workflow Foundation: Tracking Services Deep Dive

 

Ranjesh Jaganathan
Microsoft Corporation
Windows Workflow Foundation

January 2007

Applies to:
   .NET Framework 3.0/Windows Workflow Foundation
   Microsoft Visual C# Version 2.0
   Microsoft Visual Studio 2005

Summary: The Windows Workflow Foundation (WF) tracking infrastructure and services provide the facility to monitor and query the execution of a workflow instance. This article describes the tracking mechanism and the features available in WF tracking services. (24 printed pages)

See also the related article "Windows Workflow Foundation: Tracking Services Introduction" by David Gristwood.

Contents

Introduction
Tracking Overview
Tracking Profiles
SqlTrackingService
Conclusion
References

Introduction

A workflow consists of units of work called activities. Workflows provide a way to control the order of execution of the individual activities. WF uses the tracking infrastructure to monitor executing workflows.

Tracking Overview

Every running workflow instance is created and maintained by an in-process engine referred to as the Workflow Runtime Engine. The execution of the workflow can be tracked using the workflow tracking infrastructure. When a workflow instance executes, it sends events and associated data to tracking services that are registered with the workflow runtime. You can use the out-of-box SqlTrackingService or write a custom tracking service to intercept these events and use the data provided as required. Uses for tracking data include storing history information about the workflow, visualizing the workflow as it is running, or triggering some other business process.

Bb264458.wwf_tsdeepdive01(en-US,VS.80).gif

The runtime sends three types of events to the tracking service: Workflow events, Activity events, and User events. Workflow events describe the life cycle of the workflow instance. Workflow events include Created, Completed, Idle, Suspended, Resumed, Persisted, Unloaded, Loaded, Exception, Terminated, Aborted, Changed, and Started. Activity events describe the life cycle of an individual activity instance. Activity-execution status events include Executing, Closed, Compensating, Faulting, and Canceling. When creating the business logic for an activity, the activity author might want to track some business- or workflow-specific data. This can be achieved by calling any of the overloaded Activity.TrackData methods. The data tracked in this manner is sent to the tracking service as a User Tracking Event.

The designer of a workflow or activity might not want to track every single piece of information about a running workflow instance, but instead might be interested in only a subset of events. The tracking infrastructure uses Tracking Profiles to filter for events. The tracking service provides a tracking profile to the workflow runtime. The workflow runtime uses the information in the tracking profile to determine what events and data must be sent. The service sends the events via a tracking channel object, obtained via the GetTrackingChannel method of the TrackingService object. The tracking service calls the Send method of the tracking channel to send only those specific events.

The following illustration shows how the tracking service, tracking runtime, and workflow interact to create tracking data.

Bb264458.wwf_tsdeepdive02(en-US,VS.80).gif

  1. The workflow runtime requests a tracking profile for this particular instance (TryGetProfile method). The tracking service creates a tracking profile object and returns it back to the tracking runtime.
  2. The workflow runtime requests a tracking channel for this particular workflow instance (GetTrackingChannel method) from the tracking service.
  3. The tracking service creates a tracking profile object that describes what events/data needs to be provided by the runtime to the tracking service for this instance.
  4. After the workflow starts, it triggers workflow events, activity events, and user events.
  5. The tracking runtime sends the events and data, which the service requested via the tracking profile, to the tracking channel for this instance. The runtime calls the Send method of this tracking channel when it needs to send an event. The events are sent to the tracking service as they happen. By using a different tracking channel for each instance, no thread synchronization issues will arise, because each workflow always runs on a single thread, and that ensures that we will not have multiple Send calls at the same time in a given tracking channel.
  6. The tracking channel can store this information in a tracking store (SQL database in the case of the SQL tracking service).
  7. The information in the tracking store can be queried at any time.

Tracking Profiles

Tracking services often must track only a subset of the workflow information. This is achieved using tracking profiles. The tracking profile acts as a filter for tracking events. It describes what events and data must be tracked and what does not have to be tracked. After a profile is associated with the instance, only those events requested in the profile are sent to the tracking channel. The GetProfile overloads in the TrackingService object are called by the runtime to request a tracking profile object for a workflow instance.

The profile object can be created in two ways. You can either use the tracking profile object model, or use XML and serialize it to a profile object using the TrackingProfileSerializer.

The following snippet shows how to use the object model to create a TrackingProfile object. This snippet is from the User Track Points technology sample in the Tracking section of the SDK.

// Create a TrackingProfile object //
TrackingProfile profile = new TrackingProfile();
//Add an activity track point and location to the tracking profile //
ActivityTrackPoint trackPoint = new ActivityTrackPoint();
ActivityTrackingLocation location = new ActivityTrackingLocation();
location.MatchDerivedTypes = true;
//Add status events to the tracking location //
IEnumerable<ActivityExecutionStatus> statuses = 
Enum.GetValues(typeof(ActivityExecutionStatus)) as 
IEnumerable<ActivityExecutionStatus>;
foreach(ActivityExecutionStatus status in statuses)
{
    location.ExecutionStatusEvents.Add(status);
}
// Add the location to the track point //
trackPoint.MatchingLocations.Add(location);
// Add the track point to the profile //
profile.ActivityTrackPoints.Add(trackPoint);
// Specify a version for the profile //
profile.Version = new Version("3.0.0");

The following code snippet shows how to use XML and deserialize the tracking profile.

// Create a new TrackingProfileSerializer object //
TrackingProfileSerializer trackingProfileSerializer = new 
TrackingProfileSerializer();
// Create a new StringReader with the trackingProfile xml string //
StringReader reader = new StringReader(trackingProfileString);
// Deserialize into a TrackingProfile //
TrackingProfile profile = 
trackingProfileSerializer.Deserialize(reader);

The following are the contents of the tracking profile:

  • WorkflowTrackPoints
    • MatchingLocations
      • Events
    • Annotations
  • Activity TrackPoint
    • Matching/Excluded Locations
      • ActivityType
      • MatchDerivedTypes
      • ExecutionStatusEvents (for example, Executing or Closed)
      • Conditions
        • Member
        • Operator (Equals/Not Equals)
        • Value
    • Annotations
    • Extracts
      • Workflow Extract
      • Activity Extract
  • User TrackPoint
    • Matching/Excluded Locations
      • ActivityType
      • MatchDerivedActivityTypes
      • ArgumentType
      • MatchDerivedArgumentTypes
      • KeyName
      • Conditions
        • Member
        • Operator (Equals/Not Equals)
        • Value
    • Annotations
    • Extracts
      • Workflow Extract
      • Activity Extract

In the rest of this section, I will explain what the individual pieces of the profile are and provide snippets of sample code for using them.

Before we start looking at the trackpoints, let us briefly review the following: Annotations, Conditions, and Extracts. Annotations are strings that can be tagged with an event. For example, if you specify an annotation (say, "Business Data") with an Activity Trackpoint, this string will be sent along with every match for that trackpoint. Conditions help qualify events that must be tracked. An example might be to track all CodeActivities with a Name of "abc". Extracts describe which workflow's or activity's member needs to be provided during an event.

The tracking profile consists of workflow trackpoints, activity trackpoints, and user trackpoints.

Bb264458.wwf_tsdeepdive03(en-US,VS.80).gif

Workflow TrackPoint

Workflow trackpoints have a set of Matching Locations that describe which workflow events must be tracked. Annotations can also be tagged with these events using the Annotations collection.

Bb264458.wwf_tsdeepdive04(en-US,VS.80).gif

Here is a code snippet that shows how to track all workflow events and tags one annotation with the event.

WorkflowTrackPoint workflowTrackPoint = new WorkflowTrackPoint();
workflowTrackPoint.MatchingLocation = new WorkflowTrackingLocation();
foreach (TrackingWorkflowEvent workflowEvent in 
Enum.GetValues(typeof(TrackingWorkflowEvent)))
{
    workflowTrackPoint.MatchingLocation.Events.Add(workflowEvent);
}
workflowTrackPoint.Annotations.Add("This is a WorkflowTrackPoint 
annotation");
profile.WorkflowTrackPoints.Add(workflowTrackPoint);

Activity TrackPoint

Activity trackpoints describe which activity status change events must be tracked.

Bb264458.wwf_tsdeepdive05(en-US,VS.80).gif

The following snippet creates an Activity trackpoint.

ActivityTrackPoint activityTrackPoint = new ActivityTrackPoint();

Locations

Each ActivityTrackPoint has a set of matching and excluded locations. Events that fall in the list of matching locations but not the excluded locations will be tracked. Let us consider Matching Locations. A matching location is described by using the following:

  • ActivityType—Holds information about the Activity type to be matched or excluded.
  • MatchDerivedTypes—Indicates whether to match any activity deriving from this type or just this type.
  • ExecutionStatusEvents—Which execution status events to be matched or excluded.
  • ConditionsSee the following Conditions section for information.
  • Annotations—The set of strings that to be sent to the tracking service with the event.
  • Extracts—Workflow or Activity data that needs to be extracted with this event.

Conditions

Conditions can be used to describe certain events based on values of the activity's member variables. For example, you can use a condition to receive only the events for a CodeActivity named "code1". The following code snippet shows how you can achieve this.

// Activity Trackpoint to cover only Executing for activity with name 
'code1' //
// Create a new ActivityTrackpoint
ActivityTrackPoint activityTrackPoint = new ActivityTrackPoint();

// Create a new Location – ActivityType = CodeActivity
ActivityTrackingLocation activityLocation = new 
ActivityTrackingLocation(typeof(CodeActivity));

// We do not want to match types derived from CodeActivity
activityLocation.MatchDerivedTypes = false;

// We want to track only the Executing event
activityLocation.ExecutionStatusEvents.Add(ActivityExecutionStatus.Executing);

// We create a condition here to specify that the Name member of the 
Activity must be equal to "code1"
ActivityTrackingCondition activityCondition = new 
ActivityTrackingCondition("Name", "code1");
activityCondition.Operator = ComparisonOperator.Equals;

// Add the condition to the set of conditions
activityLocation.Conditions.Add(activityCondition);

// Add the location to set of matching locations
activityTrackPoint.MatchingLocations.Add(activityLocation);

Annotations

Annotations are collections of strings that can be sent with an event to the tracking services. These will help describe the event, distinguish between tracking events, or find out which track point caused the event. The following code snippet adds an annotation to an activity track point.

activityTrackPoint.Annotations.Add("This is an ActivityTrackPoint 
annotation");

The following code snippet shows how to read the received annotation in the tracking channel's Send method.

protected override void Send(TrackingRecord record)
{
    TrackingAnnotationCollection annotations = record.Annotations;
    foreach (String annotation in annotations)
    {
        Console.WriteLine("Annotation: " + annotation);
    }
}

Extracts

Extracts can be used to get Workflow or Activity data during an event. Once we have described which events will be tracked using Locations, we can provide extracts as follows.

ActivityDataTrackingExtract activityExtract1 = new 
ActivityDataTrackingExtract("Name");
WorkflowDataTrackingExtract workflowExtract1 = new 
WorkflowDataTrackingExtract("Name");
activityTrackPoint.Extracts.Add(activityExtract1);
activityTrackPoint.Extracts.Add(workflowExtract1);
profile.ActivityTrackPoints.Add(activityTrackPoint);

The above snippet shows how to extract the "Name" member of both the currently executing Activity and the Workflow.

The following code snippet uses the concepts described earlier. It creates an activity trackpoint that tracks the executing event of a code activity named "code1". We have one annotation attached to the event and extract the Names of both the Activity and the workflow during this event.

// Activity Trackpoint to cover only Executing for activity with name 
'code1' //
ActivityTrackPoint activityTrackPoint = new ActivityTrackPoint();
ActivityTrackingLocation activityLocation = new 
ActivityTrackingLocation(typeof(CodeActivity));
activityLocation.MatchDerivedTypes = false;
activityLocation.ExecutionStatusEvents.Add(ActivityExecutionStatus.Executing);
ActivityTrackingCondition activityCondition = new 
ActivityTrackingCondition("Name", "code1");
activityCondition.Operator = ComparisonOperator.Equals;
activityLocation.Conditions.Add(activityCondition);
activityTrackPoint.MatchingLocations.Add(activityLocation); 
//Annotation
activityTrackPoint.Annotations.Add("This is an ActivityTrackPoint 
annotation");
//Extracts
ActivityDataTrackingExtract activityExtract1 = new 
ActivityDataTrackingExtract("Name");
WorkflowDataTrackingExtract workflowExtract1 = new 
WorkflowDataTrackingExtract("Name");
activityTrackPoint.Extracts.Add(activityExtract1);
activityTrackPoint.Extracts.Add(workflowExtract1);
profile.ActivityTrackPoints.Add(activityTrackPoint);

User TrackPoint

Users might want to track some data during activity execution. This can be done using user trackpoints.

Bb264458.wwf_tsdeepdive06(en-US,VS.80).gif

During activity execution, you can track an object using Activity.TrackData(object data) or Activity.TrackData(string key, object data) methods. The key is a string value that will be associated with this user data and can later be used to distinguish this object.

User trackpoints are similar to activity trackpoints. The only difference is that in the locations, you can be more restrictive by specifying:

  • Which object types you want to track by using ArgumentType.
  • Whether you want to match types derived from this type.

You can also specify that you want to match specific keys using KeyName. The following is a sample user trackpoint that tracks all user events.

// Add a TrackPoint to cover all user trackpoints
UserTrackPoint userTrackPoint = new UserTrackPoint();
UserTrackingLocation userLocation = new UserTrackingLocation();
// Match all activities – any activity
userLocation.ActivityType = typeof(Activity);
userLocation.MatchDerivedActivityTypes = true;
// User data can be anything (can you be more descriptive by explaining 
that Type == object and derived == true makes it anything)
userLocation.ArgumentType = typeof(object);
userLocation.MatchDerivedArgumentTypes = true;
// Add to the matching location
userTrackPoint.MatchingLocations.Add(userLocation); 
profile.UserTrackPoints.Add(userTrackPoint);

Tracking profile sample

The following code snippet shows you how to build a tracking profile that filters for all activity tracking events from code activities with the name 'code1', as well as all workflow and user tracking events.

// Create a Tracking Profile
TrackingProfile profile = new TrackingProfile();
profile.Version = new Version("3.0.0");


// Activity Trackpoint to cover only Executing for activity with name 
'code1' 
ActivityTrackPoint activityTrackPoint = new ActivityTrackPoint();
ActivityTrackingLocation activityLocation = new 
ActivityTrackingLocation(typeof(CodeActivity));
activityLocation.MatchDerivedTypes = false;
activityLocation.ExecutionStatusEvents.Add(ActivityExecutionStatus.Executing);
ActivityTrackingCondition activityCondition = new 
ActivityTrackingCondition("Name", "code1");
activityCondition.Operator = ComparisonOperator.Equals;
activityLocation.Conditions.Add(activityCondition);
activityTrackPoint.MatchingLocations.Add(activityLocation);
// Annotation
activityTrackPoint.Annotations.Add("This is an ActivityTrackPoint 
annotation");
// Extracts
// Extract the name of the workflow and activity
ActivityDataTrackingExtract activityExtract1 = new 
ActivityDataTrackingExtract("Name");
WorkflowDataTrackingExtract workflowExtract1 = new 
WorkflowDataTrackingExtract("Name");
activityTrackPoint.Extracts.Add(activityExtract1);
activityTrackPoint.Extracts.Add(workflowExtract1);
profile.ActivityTrackPoints.Add(activityTrackPoint);

// Add a TrackPoint to cover all workflow status events
WorkflowTrackPoint workflowTrackPoint = new WorkflowTrackPoint();
workflowTrackPoint.MatchingLocation = new WorkflowTrackingLocation();
foreach (TrackingWorkflowEvent workflowEvent in 
Enum.GetValues(typeof(TrackingWorkflowEvent)))
{
    workflowTrackPoint.MatchingLocation.Events.Add(workflowEvent);
}
workflowTrackPoint.Annotations.Add("This is a WorkflowTrackPoint 
annotation");
profile.WorkflowTrackPoints.Add(workflowTrackPoint);


// Add a TrackPoint to cover all user trackpoints
UserTrackPoint userTrackPoint = new UserTrackPoint();
UserTrackingLocation userLocation = new UserTrackingLocation();
userLocation.ActivityType = typeof(Activity);
userLocation.MatchDerivedActivityTypes = true;
userLocation.ArgumentType = typeof(object);
userLocation.MatchDerivedArgumentTypes = true;
userTrackPoint.MatchingLocations.Add(userLocation);
profile.UserTrackPoints.Add(userTrackPoint);

SqlTrackingService

Overview

The out-of-box SqlTrackingService service records all the information provided by the tracking infrastructure to a SQL Server database. This information can then be queried either directly using SQL or through a provided SqlTrackingQuery interface. The following illustration shows the SQLTrackingService in operation.

Bb264458.wwf_tsdeepdive07(en-US,VS.80).gif

Creating and Using the SqlTrackingService

Before we can start using the SqlTrackingService, we must set up the SQL database. To do this, we must:

  1. Create a database or choose to use an existing database.
  2. Run the Tracking_Schema.sql and Tracking_Logic.sql scripts against the newly created, or preexisting, database. These scripts can be found at %windir%\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\<Language>\. These scripts will create the tables and the stored procedures required by the SQL tracking service.
  3. Add the SqlTrackingService to the WorkflowRuntime. This can be done programmatically or by using the application configuration file.

The following code snippet shows how to add SqlTrackingService to the runtime programmatically. The sample connection string here points to a database called 'Tracking'; if you are using a different database for tracking data, change this script accordingly.

String connectionString = "Initial Catalog=Tracking;Data Source=localhost;Integrated Security=SSPI;";
SqlTrackingService trackingService = new SqlTrackingService(connectionString);
runtime.AddService(trackingService);

To add the SqlTrackingService from the configuration file, add the following to the configuration section. For more information about the various settings for the SqlTrackingService, see the API documentation.

<WorkflowRuntime Name="Runtime">
    <Services>
      <add type="System.Workflow.Runtime.Tracking.SqlTrackingService,
                 System.Workflow.Runtime,
              Version=3.0.00000.0, Culture=neutral,
                 PublicKeyToken=31bf3856ad364e35"
                 ConnectionString="Initial Catalog=Tracking;Data 
Source=localhost;Integrated Security=SSPI;"
                 EnableRetries="false"
                 IsTransactional="true"
                 PartitionOnCompletion="false"
                 ProfileChangeCheckInterval="60000"
                 UseDefaultProfile="true"
    </Services>
</WorkflowRuntime>

Then we must add the appropriate tracking profile to the database. This can be done by using the UpdateTrackingProfile stored procedure. The stored procedure takes the workflow type, assembly name, profile version, and the tracking profile XML as parameters. If not specified, the SqlTrackingService uses a default tracking profile that tracks all activity, workflow, and user events.

After this is done, workflows of the above type will be tracked using this profile. All the information tracked will be written to the SQL database.

The following code snippet demonstrates how to add an XML tracking profile to the database (added tracking profiles are stored in the TrackingProfile table in the tracking database). This snippet is from the User Trackpoints SDK sample.

private static void InsertTrackingProfile(string profile)
        {
            {
                SqlCommand cmd = new SqlCommand();

                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandText = "dbo.UpdateTrackingProfile";
                cmd.Connection = new SqlConnection(connectionString);
                try
                {
                    cmd.Parameters.Clear();

                    SqlParameter param1 = new SqlParameter();
                    param1.ParameterName = "@TypeFullName";
                    param1.SqlDbType = SqlDbType.NVarChar;
                    param1.SqlValue = 
typeof(SimpleWorkflow).ToString();
                    cmd.Parameters.Add(param1);

                    SqlParameter param2 = new SqlParameter();
                    param2.ParameterName = "@AssemblyFullName";
                    param2.SqlDbType = SqlDbType.NVarChar;
                    param2.SqlValue = 
typeof(SimpleWorkflow).Assembly.FullName;
                    cmd.Parameters.Add(param2);


                    SqlParameter param3 = new SqlParameter();
                    param3.ParameterName = "@Version";
                    param3.SqlDbType = SqlDbType.VarChar;
                    param3.SqlValue = "3.0.0.0";
                    cmd.Parameters.Add(param3);

                    SqlParameter param4 = new SqlParameter();
                    param4.ParameterName = "@TrackingProfileXml";
                    param4.SqlDbType = SqlDbType.NText;
                    param4.SqlValue = profile;
                    cmd.Parameters.Add(param4);

                    cmd.Connection.Open();
                    cmd.ExecuteNonQuery();
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    Console.WriteLine("The Tracking Profile was not 
inserted. If you want to add a new one, increase the version 
Number.\n");
                }
                finally
                {
                    if ((null != cmd) && (null != cmd.Connection) && 
(ConnectionState.Closed != cmd.Connection.State))
                        cmd.Connection.Close();
                }
            }
        }

Transactional Mode: Batching

The SqlTrackingService runs in transactional mode by default. A work batch is associated with the workflow instance. Services can participate in this batch by implementing the IPendingWork interface. Using this batch, services can add work items to the batch. When the batch needs to be committed, the WorkflowCommitWorkBatchService calls the service with a transaction and the list of work items as parameters. The service can then complete the work items using that transaction. This provides a transactional way of tracking events. At any state, the workflow instance data in the persistence service and the tracking information in the tracking database will be consistent. This also means that tracking information is not immediately written to the tracking database and may be delayed until the next commit. Batching provides a performance advantage since multiple database writes can be grouped together.

When the batch actually commits is not controlled by the tracking service. This can cause some confusion. If the workflow does not have enough commit points, the number of tracking work items can grow quickly. Whenever the workflow persists, the work batch commits. This happens when the workflow completes or at the beginning of a transaction scope. You can also force a persist by tagging the PersistOnClose attribute on a custom activity. This forces a persist when the activity completes, essentially committing the batch.

In some cases, you might want the tracking data as soon as the events happen and delaying it might not be acceptable. In that case, batching can be turned off by setting the SqlTrackingService.IsTransactional property to false. This will cause individual tracking events to be written to the database as they happen.

Querying the SqlTrackingService Database

The information that is tracked by the SqlTrackingService can be accessed either directly through the database views or by using the SqlTrackingQuery APIs; these methods return information in the form of SqlTrackingWorkflowInstance objects. The SqlTrackingWorkflowInstance object describes a single workflow instance that is in the tracking database. To use the query API, we use three classes: SqlTrackingQuery, SqlTrackingQueryOptions, and SqlTrackingWorkflowInstance.

SqlTrackingQuery

Workflows in the SQL database can be queried by using the following methods in the SqlTrackingQuery class:

  1. SqlTrackingQuery.TryGetWorkflow(Guid instanceId, out SqlTrackingWorkflowInstance sqlInstance);
  2. SqlTrackingQuery.GetWorkflows(SqlTrackingQueryOptions options);

The SqlTrackingQuery.TryGetWorkflow method can be used to get a single SqlTrackingWorkflowInstance object corresponding to the workflow instance ID provided.

Using the SqlTrackingQuery.GetWorkflows method, you can get a set of instances that match the query criteria provided by the SqlTrackingQueryOptions parameter. ** The workflow and activity types must be resolved. These can be in either the current working directory or the Global Assembly Cache (GAC). If the types are not resolved, you will get a type not found exception.

The following code snippet prints out the activity tracking events for a particular workflow instance using the SqlTrackingQuery object.

private static void OutputActivityTrackingEvents(Guid instanceId)
{
    // Create a new SqlTrackingQuery, passing in the appropriate 
connection string
    SqlTrackingQuery sqlTrackingQuery = new 
SqlTrackingQuery(connectionString);

    // Create a SqlTrackingWorkflowInstance for use as an out parameter
    SqlTrackingWorkflowInstance sqlTrackingWorkflowInstance;
    // Get the workflow data from the query object. If present, output 
the data to the console.
    if (sqlTrackingQuery.TryGetWorkflow(instanceId, out 
sqlTrackingWorkflowInstance))
    {
        Console.WriteLine("\nActivity Tracking Events:\n");

        foreach (ActivityTrackingRecord activityTrackingRecord in 
sqlTrackingWorkflowInstance.ActivityEvents)
        {
            Console.WriteLine("StatusDescription : {0}  DateTime : {1} 
Activity Qualified Name : {2}", activityTrackingRecord.ExecutionStatus, 
activityTrackingRecord.EventDateTime, 
activityTrackingRecord.QualifiedName);
        }
    }
}

SqlTrackingQueryOptions

The SqlTrackingQueryOptions parameter to the SqlTrackingQuery.GetWorkflows method can be used to provide a set of constraints for the query. Only the matched workflows will be returned by that query. The default behavior for a newly created SqlTrackingQueryOptions is to return all workflows in the tracking database.

SqlTrackingQuery.GetWorkflows(new SqlTrackingQueryOptions());

The properties of SqlTrackingQueryOptions are as follows:

  • WorkflowType specifies the type of the workflow to match. Null matches all workflow types.
  • WorkflowStatus specifies the status to match (for example, Started, Completed, Terminated, and so on). Again, null matches everything.
  • StatusMinDateTime/StatusMaxDateTime specifies that the status has to be between these two time stamps.
  • TrackingDataItems specifies the extracts that are associated with the workflow. The workflow should have extracts with the following details.
    • ActivityName
    • FieldName
    • DataValue

The following snippet shows how to query for workflows with a specific type 'workflowType' with any status.

//Create a new SqlTrackingQuery object with the appropriate connection 
string
SqlTrackingQuery sqlTrackingQuery = new 
SqlTrackingQuery(connectionString);
// Create a new SqlTrackingQueryOptions object for use with the 
GetWorkflows method
SqlTrackingQueryOptions queryOptions = new SqlTrackingQueryOptions();

//Set the requested type to the workflow that we are concerned about 
queryOptions.WorkflowType = workflowType;
//Set the status filter to look for all status messages
queryOptions.WorkflowStatus = null;
//Retrieve the workflow instance information
IList<SqlTrackingWorkflowInstance> sqlTrackingWorkflowInstances = 
sqlTrackingQuery.GetWorkflows(queryOptions);

SqlTrackingWorkflowInstance

The SqlTrackingWorkflowInstance object describes a single workflow in the database. When querying the database using the SqlTrackingQuery object, the result is a single object or collection of SqlTrackingWorkflowInstance objects that describe the matched workflow instances in the database.

The following table describes the properties of the SqlTrackingWorkflowInstance class.

Property name Description
ActivityEvents Collection of ActivityEvents
Initialized Workflow initialization DateTime
InvokedWorkflows Workflows that this workflow has invoked, if any
InvokingWorkflowInstanceId The workflow that invoked this workflow, if any
Status Current status of the workflow
UserEvents Collection of UserEvents
WorkflowDefinition Current workflowDefinition as an Activity object
WorkflowDefinitionUpdated True if dynamic update has occurred on this workflow
WorkflowEvents Collection of workflow events
WorkflowInstanceId InstanceId of the workflow
WorkflowInstanceInternalId ID stored internally in the tracking database
WorkflowType Type of the workflow

Note   Run the QueryUsingSqlTrackingService sample now.

Data Maintenance

The SqlTrackingService tracks workflow information to tables in a SQL Database. SqlTrackingService also provides an archiving feature to help maintain a growing tracking database. There are two ways to maintain your tracking database: Partition on completion and On-demand partitioning.

Partition on Completion

Workflow instances can be moved from the main set of tables into a partition set when the workflow completes. This feature can be activated by enabling the PartitionOnCompletion flag on the SqlTrackingService. If this is activated, when the workflow completes, all the data associated with that workflow will be copied over to the currently active partition. The partition contains a new set of tables similar to the main tables. There are different partition modes like yearly, monthly, weekly, and daily. This can be changed by using the SetPartitionInterval stored procedure. The default is monthly.

Partitioning does incur a small overhead at the end of every workflow instance completion; but, as soon as the partition is not active, it can be detached/dropped by using DetachPartition and DropPartition stored procedures. Workflow instances in different partitions will also be covered when using the SqlTrackingQuery. If you want to query the database directly, use the tracking views, because they will be updated when partitions are created/dropped.

In scenarios where downtime is not an option, you can use the PartitionOnCompletion feature of the SqlTrackingService.

Note   The advantage of these stored procedures is that large-grain locks do not have to be taken in the live tables to clear out old tracking records. The process of acquiring these locks can have a significant impact on the process trying to delete the records, on the process writing new records, and on any process querying the records. The partitions allow us to remove large sets of inactive tracking record simply by dropping tables. The impact of this type of record removal is trivial.

On-Demand Partitioning

Windows Workflow Foundation also provides stored procedures to partition the workflows at a chosen time if PartitionOnCompletion is not used. If downtime is acceptable, you can run the workflows without partitioning. During downtime, you can run the PartitionCompletedWorkflowInstances stored procedure to move all the completed workflows in the main set of tables to the currently active partition tables. This procedure does not incur any cost during normal operation (instance completion).

The system does not have to be completely offline, downtime here can be defined as a period in which the system is lightly used or simply an off-peak period.

Note   Run the SqlDataMaintenance sample now.

Writing a Custom Tracking Service

For most scenarios, the out-of-box SQL tracking service that tracks the workflow information into a SQL database will suffice. In cases where the SQL tracking service does not fit your needs (such as if you must log tracking data to a different medium), you can write a custom tracking service using the tracking infrastructure. This section describes what needs to be done to write a custom tracking service. We will use the console tracking service (found in the SDK samples, in the Technologies/Tracking section) to explain the process. The console tracking service intercepts the tracking information and displays it on the console.

To write a custom tracking service, you must implement two classes: ConsoleTrackingService, which extends the TrackingService base class, and ConsoleTrackingChannel, which extends the TrackingChannel class.

Implementing the TrackingService Class

To implement a custom tracking service, we must override the profile management methods. The following table describes the methods that you must implement in a custom tracking service.

Profile-management method Description
bool TryGetProfile(Type workflowType, out TrackingProfile profile); This method is called the first time a workflow instance is created. The tracking service can provide a tracking profile object by assigning it to the profile out parameter and returning true. You can also not track anything for this instance by returning false.
protected override TrackingProfile GetProfile(Guid workflowInstanceId);

protected override TrackingProfile GetProfile(Type workflowType, Version profileVersionId);

Once the runtime has a tracking profile associated with the workflow instance, if the instance unloads, the profile object maintained internally in a cache may be garbage collected and lost. In that case, when the instance loads again, the workflow runtime needs to rebuild the cache again. To do this, the runtime uses these overloads to get a profile for either a particular instance or a specific workflow type.
protected override bool TryReloadProfile(Type workflowType, Guid workflowInstanceId, out TrackingProfile profile); The profile for a workflow instance can be replaced at any time by calling the ReloadTrackingProfiles method on the WorkflowInstance object. This calls the TryReloadProfile method on each tracking service in the runtime to update the tracking profile that is associated with this instance.

In addition to the profile-management methods, the only other method that needs to be implemented is the GetTrackingChannel method. In this example, we created a new tracking channel object and returned it. The TrackingParameters parameter of this method provides the details of the workflow instance with which the channel will be associated.

Implementing the TrackingChannel Class

To implement a custom tracking channel, we must override the profile-management methods.

TrackingChannel methods Description
protected override void Send(TrackingRecord record); The Send method will be called by the runtime for each event to provide the tracking data. The information is provided as a TrackingRecord object. This can be either an ActivityTrackingRecord, a WorkflowTrackingRecord, or a UserTrackingRecord object based on the type of tracking event (Activity, Workflow, or User).
protected override void InstanceCompletedOrTerminated(); The InstanceCompletedOrTerminated method is called when the instance completes or terminates.

To use the new tracking service, it must first be added to the workflow runtime. The following code snippet shows how the service can be added to the workflow runtime.

// Create a new Workflow Runtime //
WorkflowRuntime workflowRuntime = new WorkflowRuntime();
// Create the service //
ConsoleTrackingService consoleTrackingService = new 
ConsoleTrackingService();
// Add the service to the runtime //
workflowRuntime.AddService(consoleTrackingService);

After the tracking service is added to the runtime, the workflows will be tracked by the service.

Conclusion

The WF tracking services provide the ability to observe workflows as they execute in the runtime. The out-of-box SqlTrackingService tracks a configurable amount of information using tracking profiles to a SQL Server database. The framework provides an API for writing custom tracking services that can be configured to meet your business needs.

References

[1] "Windows Workflow Foundation: Tracking Services Introduction"

[2] Workflow on MSDN
https://msdn.microsoft.com/workflow

[3] WF MSDN Forum
https://forums.microsoft.com/msdn/showforum.aspx?forumid=122&siteid=1

[4] WF Community Site
https://wf.netfx3.com

© Microsoft Corporation. All rights reserved.