[转]WF事件驱动(4) -持久化

本文转自: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

图片 1

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

图片 2

接下来,将数据库上下文切换到WF4,依次运行两个本子

SqlWorkflowInstanceStoreSchema.sql

SqlWorkflowInstanceStoreLogic.sql

 

这数据库的构造使生趣味,可以研究一下。这里就不过大多展开了

图片 3

 

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

数据库准备好以后,我们要对宿主稍做修改,就可以形成持久化功能的安排

率先,我们要以宿主程序中补充加点儿个次集引用

图片 4

修改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进展调节

图片 5

图片 6

咱们回到数据库看一下情况,请小心看老InstancesTable

图片 7

也就是说,它都把当下四单实例保存至了数据库中。

 

下一场,我们连下去对流程展开审批

图片 8

杀明朗,236738261这个流程就终止了,我们重新来拘禁一下数据库被的笔录

图片 9

咱见到,现在数据库被之记录数也化为了3长长的。

 

 

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

既然如此生诸如此类一个数据库保存好了咱的实例,那么尽管可以放心大胆地将宿主程序关掉。

俺们来拘禁,数据库被之笔录还是以的。请小心,我为做了别一些测试,所以现在实例有5个

图片 10

 

扣押起颇不错,不是啊?

可是发生一个问题随之而来,当我们更打开应用程序的上,我们恐怕要宿主程序会半自动地加载这些实例的音信,或者说咱照样能针对这些实例进行操作。这要怎么来形成吗?

央大家按部就班自的步骤来举行练习

4.1 将宿主程序开始起来

图片 11

4.2 将客户端起来起来

图片 12

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

图片 13

图片 14

4.3 将宿主程序关掉

诸如此类做的目的,是法一下服务器突然停电了或类似这样的状。

请求不要将客户端关闭

4.4 重新开宿主程序

这般做就照葫芦画瓢服务器再开的景象。那么,问题不怕,此时客户端还能持续处理那些休形成的流水线也?

图片 15

咱得选取一个码之后,还是跟原先那么,点击“同意”或者“拒绝”按钮

图片 16

咱发现,这个流程还是可以继承处理的。而且,我们并不需要在劳动端做另外异常之规划。

故此,我们得这样总结一下:当一个流水线的伸手让发送至服务端,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();

        }


    }
}

调节之早晚,可以本着一个编号连续点击两糟糕“同意”,则第二蹩脚见面为视为不合法的伸手。如下图所示

 

图片 17

 

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;
        }

    }
}

图片 18

此地涉及一个奇之说明:InstancePromotedPropertiesTable(就是当持久化那个数据库被,本例为WF4),大家只要有时间可以拘留一下构造。它发生66独字段。

以,在是类别面临,我们尚补充加一个自定义的Activity,来贯彻真正的数码保存

图片 19

 

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 在宿主中采取该扩展,并且设定要保留之音

图片 20

 

代码也使召开相应的改,请留心红色部分

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中上加了如下三单援

图片 21

用于定义的Activity拖放咋合适岗位,并且被它的习性TicketId绑定到变量

图片 22

5.4 调试程序

起先服务器和客户端,点击多次后,到SSMS中查看
[WF4].[System.Activities.DurableInstancing].[InstancePromotedPropertiesTable]夫发明的数目

图片 23

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

实质上呢无为难,我们一般推荐以数据库中举行一个视图,如

USE WF4
GO

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

 

询问者视图的结果如下

图片 24

 

5.5 在宿主程序中经过一个奇之劳务,提供这列表给客户端

因涉嫌到数量访问,我们这边用一个LINQ to SQL Class来简化开发

图片 25

于数据库中将老视图托拽到设计器中

图片 26

用宿主代码修改如下,请留意红色部分

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 调试程序

图片 27

 

 

总:我用了季篇稿子介绍了因WF4实现审批流程的一个事例,通过实例可以协助大家又好地懂得有关的技巧。

整代码,请通过 这里
下载

相关文章