本文转自:http://www.cnblogs.com/Mayvar/archive/2011/09/03/wanghonghua201109030451.html

前面三篇,我介绍到了如何在WF 4中设计简单的审批流程,没有什么特别出奇的技术,只不过WF4对于事件机制有了不小的改进吧。

这一篇要来谈谈更加深入一点的话题:如果我们的流程需要长时间才能完成(这是很常见的),那么如何在这些流程空闲(例如等待经理审批)的时候,更好地管理它们呢?

我们都知道,默认情况下,所有流程实例都是在内存中被创建的一个对象。那么这里提到的管理,有两个层面的意思:

  1. 如果某些实例处于空闲状态,那么他们所占用的内存可能是浪费的。
  2. 由于可能因意外情况导致的宕机(例如停电,或者被某个恶作剧者按下了重启按钮),所以放在内存中的实例是很不保险的

所以,为了达到上面的两个目的,WF 提供了所谓的“持久化”的功能。就是支持我们将工作流实例通过一定的方式保存起来,等需要的时候再取出来即可。

WF3就开始支持这种特性,那时候称之为“持久化服务”。WF4对此做了进一步的改进和完善。本文主要就是讨论WF4下面如何做持久化。

完整代码,请通过 这里 下载

1. 准备持久化数据库

WF的持久化功能默认是用一个SQL Server的数据库来保存数据的。当然,在此基础上我们可以扩展。但通常使用默认的这个数据库是明智的选择。

WF4提供了两个脚本,可以让我们来生成这个数据库。这两个脚本通常在下面的目录

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SQL\en

【注意】如果你不是x64的系统,则可能是C:\Windows\Microsoft.NET\Framework\v4.0.30319\SQL\en

我们可以手工先在SQL Server Management Studio中创建一个数据库,例如叫WF4

然后,将数据库上下文切换到WF4,依次运行两个脚本

SqlWorkflowInstanceStoreSchema.sql

SqlWorkflowInstanceStoreLogic.sql

这个数据库的结构如果有兴趣,可以研究一下。这里就不过多展开了

2. 修改宿主程序,添加持久化服务的功能

数据库准备好之后,我们需要对宿主稍做修改,就可以完成持久化功能的配置

首先,我们需要在宿主程序中添加两个程序集引用

修改Main方法代码如下 (请注意红色字体)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Activities;
using System.ServiceModel.Activities;
using System.ServiceModel.Description;using System.Activities.DurableInstancing;
using System.Runtime.DurableInstancing;
using System.Activities.Persistence;
using System.ServiceModel.Activities.Description;
using System.Xml.Linq;namespace Host
{class Program{static void Main(string[] args){var host = new WorkflowServiceHost(new DocumentReviewLib.DocumentReviewWorkflow(),new Uri("http://localhost:8080/DRS"));host.AddDefaultEndpoints();//这个方法是添加了一些标准的端点host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });host.AddServiceEndpoint("IMetadataExchange",MetadataExchangeBindings.CreateMexHttpBinding(),"mex");            var store = new SqlWorkflowInstanceStore("server=(local)\\sqlexpress;database=WF4;integrated security=true");host.DurableInstancingOptions.InstanceStore = store;host.Open();Console.WriteLine("Server is ready.");Console.Read();}}
}

这样就可以完成服务的配置了。当然,我们这里仅仅是为了直观期间,用了代码的方式(而且用的是最简单的做法)。在生产环境下,我们可能会倾向于用配置文件的方式,而不是代码。

事实上,我个人认为WCF,WF中一个很大的亮点就是减少了对代码的依赖度,确实还是做得不错的。

3. 调试程序

按下F5进行调试

我们回到数据库看一下情况,请注意看那个InstancesTable

也就是说,它已经把这四个实例保存到了数据库中。

然后,我们接下来对流程进行审批

很显然,236738261这个流程已经结束了,我们再来看一下数据库中的记录

我们看到,现在数据库中的记录数也变为了3条。

4.如何加载已经保存好的实例

既然有这样一个数据库保存好了我们的实例,那么就可以放心大胆地将宿主程序关闭掉。

我们来看,数据库中的记录仍然是在的。请注意,我因为做了其他一些测试,所以现在实例有5个

看起来很不错,不是吗?

但是有一个问题随之而来,当我们再次打开应用程序的时候,我们可能希望宿主程序能自动地加载这些实例的信息,或者说我们仍然能够对这些实例进行操作。这要如何来完成呢?

请大家按照我的步骤来做练习

4.1 将宿主程序开起来

4.2 将客户端开起来

点击“创建流程”按钮,可以多点几次

4.3 将宿主程序关闭掉

这样做的目的,是模拟一下服务器突然停电了或者类似这样的情况。

请不要将客户端关闭

4.4 重新开启宿主程序

这样做就模拟服务器重启的场景。那么,问题就是,此时客户端还能继续处理那些未完成的流程吗?

我们可以选择一个编号之后,还是和以前那样,点击“同意”或者“拒绝”按钮

我们发现,这个流程还是可以继续处理的。而且,我们并不需要在服务端做任何特殊的设计。

所以,我们可以这么总结一下:当一个流程的请求被发送到服务端,WorkflowServiceHost会收到,它先在内存中查找看是否有合适的实例,如果没有,则会尝试查看数据库中是否有合适的实例,如果有,则会加载它

那么,如果在内存和数据库都没有实例的话,会怎么样呢?(例如某个流程已经被处理完了,你还是硬要继续审批)。这种情况下,WorkflowServiceHost会将这个请求列为所谓的错误的消息。

为了证明这一点,我们对服务器代码稍作修改(请注意红色部分)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Activities;
using System.ServiceModel.Activities;
using System.ServiceModel.Description;using System.Activities.DurableInstancing;
using System.Runtime.DurableInstancing;
using System.Activities.Persistence;
using System.ServiceModel.Activities.Description;
using System.Xml.Linq;namespace Host
{class Program{static void Main(string[] args){var host = new WorkflowServiceHost(new DocumentReviewLib.DocumentReviewWorkflow(),new Uri("http://localhost:8080/DRS"));host.AddDefaultEndpoints();//这个方法是添加了一些标准的端点host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });host.AddServiceEndpoint("IMetadataExchange",MetadataExchangeBindings.CreateMexHttpBinding(),"mex");var store = new SqlWorkflowInstanceStore("server=(local)\\sqlexpress;database=WF4;integrated security=true");host.DurableInstancingOptions.InstanceStore = store;            host.UnknownMessageReceived += (o, e) =>{Console.WriteLine("\n"+e.Message+"\n");};host.Open();Console.WriteLine("Server is ready.");Console.Read();}}
}

调试的时候,可以对一个编号连续点击两次“同意”,则第二次会被视为不合法的请求。如下图所示

5. 如何在客户端获取待处理任务列表

在上一个练习中,我们为了测试服务器端会自动检索那些未完成的流程实例,我们将宿主程序关闭后再打开了,但是我也特别提醒大家,不要将客户端程序关闭。

为什么呢?因为如果你关闭了,那些编号就没有了,而我们UpdateTicket操作是要根据TicketId进行操作的。

那么,就引申出来一个更大的问题,客户端不可能永远开着的,那么这些未完成流程的TicketId要保存在哪里?而客户端又如何能获取到这个列表呢?

有的朋友可能会说,我们可以单独搞一个数据库吧,用一个表来保存这些信息好了。那当然是可以的,但并不见得是很好的一个做法。

在WF4所提供的持久化功能中,考虑到了这种问题。它可以用一个特殊的表保存我们流程运转期间的一些数据。这里姑且称为“流程数据”吧

为了实现这样的功能,需要对持久化进行必要的扩展,请大家按照我下面的步骤来操作。

5.1 创建一个PersistenceParticipant

这是所谓的持久化参与者。它将在持久化的过程中起一定的作用。

为了便于复用,我们单独创建了一个ClassLibrary,取名为Extensions

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities.Persistence;
using System.Xml.Linq;namespace Extensions
{public class MyInstanceStoreParticpant : PersistenceParticipant{public int TicketId { get; set; }XNamespace xNS = XNamespace.Get("http://xizhang.com/DocumentReview");/// <summary>/// 这个方法会在工作流实例被持久化的时候自动调用/// 这些数据是会被保存到InstancePromotedPropertiesTable这个表的/// </summary>/// <param name="readWriteValues"></param>/// <param name="writeOnlyValues"></param>protected override void CollectValues(out IDictionary<XName, object> readWriteValues, out IDictionary<XName, object> writeOnlyValues){readWriteValues = new Dictionary<XName, object>();readWriteValues.Add(xNS.GetName("TicketId"), this.TicketId);writeOnlyValues = null;}}
}

这里提到一个特殊的表:InstancePromotedPropertiesTable(就是在持久化那个数据库中,本例为WF4),大家如果有时间可以看一下结构。它有66个字段。

同时,在这个项目中,我们还添加一个自定义的Activity,来实现真正的数据保存

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;namespace Extensions
{public sealed class SetTicket : CodeActivity{public InArgument<int> TicketId { get; set; }protected override void Execute(CodeActivityContext context){var extension = context.GetExtension<MyInstanceStoreParticpant>();extension.TicketId = TicketId.Get(context);}}
}

5.2 在宿主中使用该扩展,并且设定要保存的信息

代码也要做相应的修改,请注意红色部分

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Activities;
using System.ServiceModel.Activities;
using System.ServiceModel.Description;using System.Activities.DurableInstancing;
using System.Runtime.DurableInstancing;
using System.Activities.Persistence;
using System.ServiceModel.Activities.Description;
using System.Xml.Linq;namespace Host
{class Program{static void Main(string[] args){var host = new WorkflowServiceHost(new DocumentReviewLib.DocumentReviewWorkflow(),new Uri("http://localhost:8080/DRS"));host.AddDefaultEndpoints();//这个方法是添加了一些标准的端点host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });var store = new SqlWorkflowInstanceStore("server=(local)\\sqlexpress;database=WF4;integrated security=true");host.UnknownMessageReceived += (o, e) =>{Console.WriteLine("\n"+e.Message+"\n");};            host.Description.Behaviors.Add(new WorkflowIdleBehavior(){TimeToPersist = TimeSpan.FromSeconds(0)});XNamespace xNS = XNamespace.Get("http://xizhang.com/DocumentReview");store.Promote("DocumentReview",new List<XName>() { xNS.GetName("TicketId") },null);host.WorkflowExtensions.Add(new Extensions.MyInstanceStoreParticpant());host.DurableInstancingOptions.InstanceStore = store;host.Open();Console.WriteLine("Server is ready.");Console.Read();}}
}

5.3 使用工作流设计,使用自定义的Activity

请确保在DocumentReviewLib中添加了如下三个引用

将自定义的Activity拖放咋合适位置,并且让它的属性TicketId绑定到变量

5.4 调试程序

启动服务器和客户端,点击多次后,到SSMS中查看 [WF4].[System.Activities.DurableInstancing].[InstancePromotedPropertiesTable]这个表的数据

那么,怎么查询这些数据呢?

其实也不难,我们一般推荐在数据库中做一个视图,如

USE WF4
GOCREATE VIEW DocumentReviewTask
AS
SELECT [SurrogateInstanceId],[PromotionName],[Value1] AS TicketId
FROM [WF4].[System.Activities.DurableInstancing].[InstancePromotedPropertiesTable]

查询这个视图的结果如下

5.5 在宿主程序中通过一个特殊的服务,提供这个列表给客户端

因为涉及到数据访问,我们这里用一个LINQ to SQL Class来简化开发

从数据库中将那个视图托拽到设计器中

将宿主代码修改如下,请注意红色部分

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Activities;
using System.ServiceModel.Activities;
using System.ServiceModel.Description;using System.Activities.DurableInstancing;
using System.Runtime.DurableInstancing;
using System.Activities.Persistence;
using System.ServiceModel.Activities.Description;
using System.Xml.Linq;namespace Host
{class Program{static void Main(string[] args){var host = new WorkflowServiceHost(new DocumentReviewLib.DocumentReviewWorkflow(),new Uri("http://localhost:8080/DRS"));host.AddDefaultEndpoints();//这个方法是添加了一些标准的端点host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });var store = new SqlWorkflowInstanceStore("server=(local)\\sqlexpress;database=WF4;integrated security=true");host.UnknownMessageReceived += (o, e) =>{Console.WriteLine("\n"+e.Message+"\n");};host.Description.Behaviors.Add(new WorkflowIdleBehavior(){TimeToPersist = TimeSpan.FromSeconds(0)});XNamespace xNS = XNamespace.Get("http://xizhang.com/DocumentReview");store.Promote("DocumentReview",new List<XName>() { xNS.GetName("TicketId") },null);host.WorkflowExtensions.Add(new Extensions.MyInstanceStoreParticpant());host.DurableInstancingOptions.InstanceStore = store;host.Open();
            var common = new ServiceHost(typeof(CommonService),new Uri("http://localhost:8080/Common"));common.AddServiceEndpoint(typeof(ICommonService).FullName,new BasicHttpBinding(),"");common.Open();
 Console.WriteLine("Server is ready."); Console.Read(); } }  [ServiceContract] public interface ICommonService { [OperationContract] int[] GetTicketIds(); } public class CommonService : ICommonService { public int[] GetTicketIds() { var ctx = new InstanceStoreDataContext(); return ctx.DocumentReviewTasks.Select(r => (int)r.TicketId).ToArray(); } } }

5.6 修改客户端,使用该服务

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel.Activities;
using System.ServiceModel;namespace Client
{
    [ServiceContract]public interface ICommonService{[OperationContract]int[] GetTicketIds();}public partial class Form1 : Form{public Form1(){InitializeComponent();Load += new EventHandler(Form1_Load);}        void Form1_Load(object sender, EventArgs e){LoadTaskList();}private void LoadTaskList(){//加载所有没有完成的流程var factory = new ChannelFactory<ICommonService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:8080/Common"));var proxy = factory.CreateChannel();var ids = proxy.GetTicketIds();foreach (var item in ids){lstTickets.Items.Add(item);}}private void btCreate_Click(object sender, EventArgs e){var proxy = new DocumentReviewClient();var result = proxy.CreateTicket();lstTickets.Items.Add(result);}private void btApproval_Click(object sender, EventArgs e){//同意某个流程var action = "approval";UpdateTicket(action);}private void UpdateTicket(string action){if (lstTickets.SelectedIndex > -1){var id = int.Parse(lstTickets.SelectedItem.ToString());var comment = txtComment.Text;var proxy = new DocumentReviewClient();proxy.UpdateTicket(action, comment, id);}}private void btReject_Click(object sender, EventArgs e){var action = "Reject";UpdateTicket(action);}}
}

5.7 调试程序

 
 

总结:我用了四篇文章介绍了基于WF4实现审批流程的一个例子,通过实例可以帮助大家更好地理解有关的技术。

完整代码,请通过 这里 下载

转载于:https://www.cnblogs.com/freeliver54/archive/2013/02/17/2914481.html

[转]WF事件驱动(4) -持久化相关推荐

  1. [转]WF事件驱动(1)

    本文转自:http://www.cnblogs.com/Mayvar/archive/2011/09/03/wanghonghua_201109030446.html 已经有不少朋友知道Workflo ...

  2. 【翻译】WF从入门到精通(第六章):加载和卸载实例

    上一篇:[翻译]WF从入门到精通(第五章):workflow跟踪 学习完本章,你将掌握:     1.理解工作流实例为什么要卸载和重新加载及其时机     2.理解工作流实例为什么要持久化及其时机   ...

  3. 业务工作流平台设计(七)

    自定义活动(四) 保持状态的一致性及完整性 上一节讲过,要完成一个审核活动所要处理的数据有下面的内容 l工件进行编辑并进行保存 l记录工件的审核情况 l运行完后保存WF实例的状态 通过上面我们可以看到 ...

  4. 实际工作中遇到的技术难题与大家交流(工作流条件表达式计算部分),希望技术高手能给于指点

    有一个审核的工作流程,默认情况下是 [杭州编辑审核]--> [北京编辑审核]--> [信息发布员审核]--> [信息发布] 这个是一个典型的工作审核流程,我们可以简单的建立3个角色, ...

  5. asp.net中的报销多级审批工作流

    最近正在学习工作流,正好从网上搜索到一个 Asp.net工作流(WWF+LINQ)的例子,之前学习MOSS时接触过工作流,不过那是针对MOSS的工作流,我一直从事B/S架构开发,知道工作流可用于很多环 ...

  6. WF 4.0 之持久化操作一:SqlServer方式的存储

    没有持久化的WF 能称为一个完整的WF吗,答案是否定的:如果WF不能持久化,那么流程就需要一次就执行完毕,所有的操作就要一次走下去,可现实中的工作流是这样吗,答案同样是否定的.一个投票流程需要多个评委 ...

  7. 完成了WF工作流持久化和对持久化介质数据的加载, 但是仅仅用持久化,不能够保存工作流当前的执行状态,需要跟踪服务支持,怎样使用Tracing 服务呢?...

    配置持久化服务(3步): 1.创建和配置持久化数据库 2.添加SQLStatePersistanceService实例对象到WorkFlow运行时中; 3.保存工作流实例状态. 将工作流持久化服务添加 ...

  8. 【深入浅出WF】——持久化的过程

    原文:http://msdn.microsoft.com/en-us/library/ee473462%28VS.100%29.aspx 实例存储 一个实例存储是一个实例的逻辑容器.它存储了实例数据和 ...

  9. 聊聊事件驱动的架构模式

    作者 | Natan Silnitsky 来源 | Wix 工程博客 最近经常听到谁谁谁用事件驱动了,正好看到一篇不错的关于事件架构的文章,分享给你,希望对你有帮助,以下是正文. 在过去一年里,我一直 ...

最新文章

  1. php实时股票,php获得股票数据
  2. OpenCV位姿与投影变换
  3. 使用debug工具修改寄存器中的值
  4. 苹果公司透露Siri新发音引擎的内部原理
  5. 两个class写在同一个java文件中
  6. 单片机搭建环境烧录方法_单片机仿真器的工作原理解析
  7. 20万数据 sql 快还是 java快?_基于SpringBoot2.0开发的,轻量级的,前后分离Java开发平台...
  8. 简易留言板HTML+JS代码
  9. 计算机维修与维护怎么学,做电脑维修需要学习哪些知识呢?
  10. Axure 画原型图
  11. Linux 配置vim
  12. 服务器卡顿修改dns,电视/盒子太卡了怎么办?教你修改DNS解决卡顿问题
  13. 28个Java开发常用规范技巧总结
  14. 第七课 实战文件注册机制
  15. 树莓派存储方案_树莓派网络存储(NAS)
  16. 西电机器学习简答题核心考点汇总(期末真题,教材西瓜书)
  17. 【随记】无线网络能替代有线网络吗?
  18. 教授专栏16 | 李家涛: 善用科技拓新价值 是经营致胜关键
  19. 水银开关控制LED灯灯灭
  20. MYSQL学习笔记(自用)第三章

热门文章

  1. PAT1001. 害死人不偿命的(3n+1)猜想
  2. java入门基础重要知识必考考点
  3. dataframe保存为txt_Word,PDF,PPT,TXT之间的相互转换方法
  4. 嘘,我已经瞒着开发解锁APP日志文件抓取及分析啦!
  5. Recipe terminated with error. vscode latex-workshop新的配置文件
  6. IOHK与World Mobile合作以在坦桑尼亚建立新移动网络
  7. 韦氏评级:担心比特币近期价格走势的人都过于关注短期
  8. SAP License:SAP不便解决的问题之七——权限问题
  9. SAP License:SAP凭证编号中跳号问题处理
  10. 读懂现金贷产品的客群风险标签维度