晕了吗?在Windows Workflow中有两种跟踪:Trace和Tracking。虽然这两个它们都可以用来曝露工作流Runtime内部重要事件的细节,但Tracking信息是通过序列化API曝露给Windows Workflow的。在本节,我们将会看到Tracking信息是如何通过WF提供的API保存在数据库里的。 |
6.4.1 跟踪类
所有的跟踪服务都继承自TrackingService基类。这个类定义了运行跟踪配置和跟踪信道的API。上面曾提到,跟踪配置定义并过滤了我们需要从Runtime接收的信息的类型。而跟踪信道则是则是连接工作流Runtime和跟踪服务的通信管道。Runtime会向跟踪服务索取一个基于跟踪配置的跟踪信道。一旦Runtime获得了开放的信道,它就会通过这个信道给跟踪服务发送信息。我们在编写自定义跟踪服务的同时也需要提供相应的跟踪信道。
Windows Workflow提供了一个现成的跟踪服务的实现:SqlTrackingService类。SqlTrackingService将接收到的跟踪信息和跟踪配置储存在SQL Server数据库中。
![](https://i-blog.csdnimg.cn/blog_migrate/5e9418d866c510a17bcc9c16b3c9822a.png)
和SQL持久化服务一样,使用SQL跟踪服务也需要执行架构脚本来创建必须的数据库结构。我们也可以在SQL持久化数据库中执行SQL跟踪服务的架构脚本。创建跟踪架构的脚本位于和持久化脚本相同的位置(经典的C:\WINDOWS\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL)。脚本的文件名是Tracking_Schema.sql和Tracking_Logic.sql。我们必须先执行架构脚本,然后再执行逻辑脚本。下图演示了如何使用命令行工具来执行脚本:
![](https://i-blog.csdnimg.cn/blog_migrate/90d865a8756060246e11ab1cbc3c4515.png)
6.4.2 跟踪配置
我们可以通过编程或者应用程序配置文件来配置跟踪服务。下面的配置文件将会按照默认的参数加载SQL跟踪服务:
< configuration >
< configSections >
< section name ="WorkflowWithTracking"
type ="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</ configSections >
< WorkflowWithTracking >
< CommonParameters >
< add name ="ConnectionString"
value ="Data Source=.;Initial Catalog=c6ps;Integrated Security=True" />
</ CommonParameters >
< Services >
< add type ="System.Workflow.Runtime.Tracking.SqlTrackingService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35" />
</ Services >
</ WorkflowWithTracking >
</ configuration >
下表列出了可以传递给服务的参数:
参数名称 | 描述 |
ConnectionString | 跟踪数据库的连接字符串。 |
EnableRetries | 当值为true时,如果连接跟踪数据库失败,服务就会尝试重新连接(最多尝试20次),默认值是false。 |
IsTransactional | 当值为true时,服务会像持久化服务那样可以参与到事务中,默认值是true。 |
PartitionOnCompletion | 为了更长的运行时间和更高的可伸缩性,分区功能(partitioning feature)会定期创建一系列新的数据表来存储完成的工作流实例。默认值是false。 |
ProfileChangeCheckInterval | 服务会缓存跟踪配置,但也会定期检查跟踪配置是否发生变化。默认的检查时间间隔是1分钟。 |
UseDefaultProfile | 当值为true时,会在没有定义任何跟踪配置的情况下返回默认跟踪配置,默认值是true。 |
6.4.3 运行跟踪服务
在配置文件中配置了跟踪服务之后,我们不需要对Runtime做什么特别的改动,下面是创建Runtime并运行工作流的代码:
{
using (WorkflowRuntime runtime = new WorkflowRuntime( " WorkflowWithTracking " ))
using (AutoResetEvent reset = new AutoResetEvent( false ))
{
runtime.WorkflowCompleted += delegate { reset.Set(); };
runtime.WorkflowTerminated += delegate { reset.Set(); };
WorkflowInstance instance;
instance = runtime.CreateWorkflow( typeof (WorkflowWithTrackingService));
instance.Start();
reset.WaitOne();
DumpTrackingEvents(instance.InstanceId);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/79a10f525d7b103baa78aa72d22d7d6f.png)
SqlTrackingWorkflowInstance类是我们访问指定工作流实例的跟踪信息的入口。它的ActivityEvents属性会返回一个ActivityTrackingRecord对象集合。同样的,WorkflowEvents属性会返回WorkflowTrackingRecord对象集合,UserEvents属性会返回UserTrackingRecord对象集合(UserEvents就是我们可以自定义的事件)。而且需要注意的是这些信息中还包含了时间戳、参数和状态代码。SqlTrackingWorkflowInstance类还包含了一个WorkflowDefinition属性,可以返回一个工作流的XAML定义,这个功能在审核使用了动态更新或者为每个客户端进行了定制的工作流时非常有用。
下面的代码使用了这些类来获取一些跟踪信息。通过传递连接字符串和工作流实例ID,SqlTrackingQuery类可以返回一个SqlTrackingWorkflowInstance对象,我们可以通过这个对象来访问该工作流实例的所有记录。连接字符串可以从应用程序配置文件中获取,而实例ID则作为参数来指定。
{
WorkflowRuntimeSection config;
config = ConfigurationManager.GetSection( " WorkflowWithTracking " ) as WorkflowRuntimeSection;
SqlTrackingQuery query = new SqlTrackingQuery();
query.ConnectionString = config.CommonParameters[ " ConnectionString " ].Value;
SqlTrackingWorkflowInstance trackingInstace;
query.TryGetWorkflow(instanceID, out trackingInstace);
if (trackingInstace != null )
{
Console.WriteLine( " Tracking Information for {0} " , instanceID);
Console.WriteLine( " Workflow Events " );
foreach (WorkflowTrackingRecord r in trackingInstace.WorkflowEvents)
{
Console.WriteLine( " Date: {0}, Status: {1} " ,
r.EventDateTime, r.TrackingWorkflowEvent);
}
Console.WriteLine( " Activity Events " );
foreach (ActivityTrackingRecord r in trackingInstace.ActivityEvents)
{
Console.WriteLine( " Activity: {0} Date: {1} Status: {2} " ,
r.QualifiedName, r.EventDateTime, r.ExecutionStatus);
}
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/517799652cdcde76fdfa9940f96927c9.png)
运行简单的工作流都会生成大量的跟踪信息(上个示例中我们看到的只是其中一小部分而已)。SQL跟踪服务提供了一个默认的跟踪配置来处理Runtime生成的所有信息。如果我们只想跟踪特定的信息,就需要自定义一个跟踪配置。
6.4.3.1 跟踪配置
当工作流Runtime创建了新的工作流实例后,它会调用每个跟踪服务的TryGetProfile方法,并将工作流实例作为参数传递给这个方法。如果有针对工作流类型配置过的跟踪配置,TryGetProfile方法就会通过输出参数返回TrackingProfile类型的跟踪配置。Runtime过滤了跟踪信息,然后使用跟踪配置中定义的跟踪点来把它们发送到服务。下图演示了创建跟踪配置的相关类:
![](https://i-blog.csdnimg.cn/blog_migrate/fd9c06b11aa608595b8c4f79b65e17bc.png)
如果我们不想跟踪工作流内部的活动,而只是想跟踪工作流自身的信息,那么就需要定义和创建一个新的TrackingProfile对象并为它的WorkflowTrackPoints添加数据。而ActivityTrackPoints和UserTrackPoints属性将会被留空。
profile.Version = new Version( " 1.0.0 " );
WorkflowTrackPoint workflowTrackPoint = new WorkflowTrackPoint();
Array statuses = Enum.GetValues( typeof (TrackingWorkflowEvent));
foreach (TrackingWorkflowEvent status in statuses)
{
workflowTrackPoint.MatchingLocation.Events.Add(status);
}
profile.WorkflowTrackPoints.Add(workflowTrackPoint);
string profileAsXml = SerializeProfile(profile);
UpdateTrackingProfile(profileAsXml);
上面的代码在WorkflowTrackPoints集合中添加了我们想要跟踪的工作流事件。通过使用对TrackingWorkflowEvent使用Enum.GetValues,我们可以获取所有可能发生的事件,包括Started、Comleted、Persisted和Terminated等等。
创建了TrackingProfile对象之后,还需要在跟踪服务里更新跟踪配置。更新过程的第一步是将配置对象序列化为XML。TrackingProfileSerializer将会为我们执行序列化动作。
{
TrackingProfileSerializer serializer;
serializer = new TrackingProfileSerializer();
StringWriter writer = new StringWriter( new StringBuilder());
serializer.Serialize(writer, profile);
return writer.ToString();
}
{
WorkflowRuntimeSection config;
config = ConfigurationManager.GetSection( " WorkflowWithTracking " )
as WorkflowRuntimeSection;
using (SqlConnection connection = new SqlConnection())
{
connection.ConnectionString = config.CommonParameters[ " ConnectionString " ].Value;
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.CommandType = CommandType.StoredProcedure;
command.CommandText = " dbo.UpdateTrackingProfile " ;
command.Parameters.Add( new SqlParameter( " @TypeFullName " ,
typeof (WorkflowWithTrackingService).ToString()));
command.Parameters.Add( new SqlParameter( " @AssemblyFullName " ,
typeof (WorkflowWithTrackingService).Assembly.FullName));
command.Parameters.Add( new SqlParameter( " @Version " ,
" 1.0.1 " ));
command.Parameters.Add( new SqlParameter( " @TrackingProfileXml " ,
profileAsXml));
connection.Open();
command.ExecuteNonQuery();
}
}
在运行程序后,我们可以通过数据库中的跟踪配置来记录不同的信息。其它工作流则仍然使用默认跟踪配置并记录所有事件。下图演示了示例运行的输出结果,可以看到跟踪服务记录了工作流事件,但却没有记录任何活动事件。
![](https://i-blog.csdnimg.cn/blog_migrate/7c9a6a699cff33733af77729f283dcb1.png)
6.4.4 数据维护
SQL跟踪服务提供了一个分区功能来将跟踪信息从跟踪数据表的主数据集(primary data set)移动到分区数据集(partitioned data set)中。这个功能帮助我们管理这些不断增长的跟踪数据。管理员可以移动并归档旧的跟踪信息,并且可以确保当前正在使用的数据分区表永远不会变得过分庞大。
当分区执行时,会为每一段已经过去了的分区时间间隔创建新数据集的数据表。SetPartionInterval存储过程可以配置分区时间间隔。默认的时间间隔是一个月,其它可选的值包括一天、一周和一年。分区功能创建的数据表的名称里会包含分区时间。执行分区动作有两种方式:自动分区和手工分区。自动分区可以通过将SQL跟踪服务的PartitionOnCompletion参数配置为true来启用。自动分区会在工作流执行完之后马上将跟踪信息移动到新的分区。自动分区适用于那些从不休止的应用程序,但因为每次当工作流完成时都需要记录跟踪信息到分区数据集中,所以会增加一些开销。
我们还可以通过运行PartitionCompletedWorkflowInstances存储过程来使用手工分区。这个存储过程会将已经完成的工作流的跟踪记录移动到分区表中。手工分区适用于那些总会有一段停机时间的应用程序,这样我们就可以选择在非繁忙时间来执行手工分区
Lad.Li