WF 4.0 之持久化操作二:Xml方式的存储
在上一篇中提到了InstanceStore和PersistenceParticipant,这两个是实现自定义存储的关键,也是我们两个自定义类分别继承的基类。InstanceStore为持久化的基类,PersistenceParticipant为进行持久化准备数据过程提供了方便。
在这个实例中一共有两个主要的类,一个是XmlPersistenceParticipant用于收集要持久化的信息,一个是XmlWorkflowInstanceStore用于保存到指定的文件去。
Xml方式持久化实现之自定义持久化类:
public class XmlWorkflowInstanceStore : InstanceStore { Guid ownerInstanceID; public XmlWorkflowInstanceStore() : this(Guid.NewGuid()) { } public XmlWorkflowInstanceStore(Guid id) { ownerInstanceID = id; } /// <summary> /// 确定是否进行异步进行持久化操作 /// </summary> /// <param name="context"></param> /// <param name="command"></param> /// <param name="timeout"></param> /// <returns></returns> protected override bool TryCommand(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout) { return EndTryCommand(BeginTryCommand(context, command, timeout, null, null)); } /// <summary> /// 异步进行持久化操作 /// </summary> /// <param name="context"></param> /// <param name="command"></param> /// <param name="timeout"></param> /// <param name="callback"></param> /// <param name="state"></param> /// <returns></returns> protected override IAsyncResult BeginTryCommand(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state) { IDictionary<System.Xml.Linq.XName, InstanceValue> data = null; //创建持久化工作流 if (command is CreateWorkflowOwnerCommand) { context.BindInstanceOwner(ownerInstanceID, Guid.NewGuid()); } //修改持久化的工作流 else if (command is SaveWorkflowCommand) { SaveWorkflowCommand saveCommand = (SaveWorkflowCommand)command; data = saveCommand.InstanceData; Save(data); } //加载持久化的工作流 else if (command is LoadWorkflowCommand) { string fileName = IOHelper.GetFileName(this.ownerInstanceID); try { using (FileStream inputStream = new FileStream(fileName, FileMode.Open)) { data = LoadInstanceDataFromFile(inputStream); context.LoadedInstance(InstanceState.Initialized, data, null, null, null); } } catch (Exception exception) { throw new PersistenceException(exception.Message); } } return new CompletedAsyncResult<bool>(true, callback, state); } protected override bool EndTryCommand(IAsyncResult result) { return CompletedAsyncResult<bool>.End(result); } /// <summary> /// 从指定的文件中读取工作流信息和数据 /// </summary> /// <param name="inputStream"></param> /// <returns></returns> IDictionary<System.Xml.Linq.XName, InstanceValue> LoadInstanceDataFromFile(Stream inputStream) { IDictionary<System.Xml.Linq.XName, InstanceValue> data = new Dictionary<System.Xml.Linq.XName, InstanceValue>(); NetDataContractSerializer s = new NetDataContractSerializer(); XmlReader rdr = XmlReader.Create(inputStream); XmlDocument doc = new XmlDocument(); doc.Load(rdr); XmlNodeList instances = doc.GetElementsByTagName("InstanceValue"); foreach (XmlElement instanceElement in instances) { XmlElement keyElement = (XmlElement)instanceElement.SelectSingleNode("descendant::key"); System.Xml.Linq.XName key = (System.Xml.Linq.XName)DeserializeObject(s, keyElement); XmlElement valueElement = (XmlElement)instanceElement.SelectSingleNode("descendant::value"); object value = DeserializeObject(s, valueElement); InstanceValue instVal = new InstanceValue(value); data.Add(key, instVal); } return data; } /// <summary> /// 反序列化 /// </summary> /// <param name="serializer"></param> /// <param name="element"></param> /// <returns></returns> object DeserializeObject(NetDataContractSerializer serializer, XmlElement element) { object deserializedObject = null; MemoryStream stm = new MemoryStream(); XmlDictionaryWriter wtr = XmlDictionaryWriter.CreateTextWriter(stm); element.WriteContentTo(wtr); wtr.Flush(); stm.Position = 0; deserializedObject = serializer.Deserialize(stm); return deserializedObject; } /// <summary> /// 保存数据到xml /// </summary> /// <param name="instanceData"></param> void Save(IDictionary<System.Xml.Linq.XName, InstanceValue> instanceData) { string fileName = IOHelper.GetFileName(this.ownerInstanceID); XmlDocument doc = new XmlDocument(); doc.LoadXml("<InstanceValues/>"); foreach (KeyValuePair<System.Xml.Linq.XName,InstanceValue> valPair in instanceData) { XmlElement newInstance = doc.CreateElement("InstanceValue"); XmlElement newKey = SerializeObject("key", valPair.Key, doc); newInstance.AppendChild(newKey); XmlElement newValue = SerializeObject("value", valPair.Value.Value, doc); newInstance.AppendChild(newValue); doc.DocumentElement.AppendChild(newInstance); } doc.Save(fileName); } /// <summary> /// 序列化 /// </summary> /// <param name="elementName"></param> /// <param name="o"></param> /// <param name="doc"></param> /// <returns></returns> XmlElement SerializeObject(string elementName, object o, XmlDocument doc) { NetDataContractSerializer s = new NetDataContractSerializer(); XmlElement newElement = doc.CreateElement(elementName); MemoryStream stm = new MemoryStream(); s.Serialize(stm, o); stm.Position = 0; StreamReader rdr = new StreamReader(stm); newElement.InnerXml = rdr.ReadToEnd(); return newElement; } }
class XmlPersistenceParticipant : PersistenceParticipant { const string propertiesNamespace = "urn:schemas-microsoft-com:System.Activities/4.0/properties"; private Guid Id; public XmlPersistenceParticipant(Guid id) { Id = id; } /// <summary> /// 收集保存的信息 /// </summary> /// <param name="readWriteValues"></param> /// <param name="writeOnlyValues"></param> protected override void CollectValues(out IDictionary<XName, object> readWriteValues, out IDictionary<XName, object> writeOnlyValues) { base.CollectValues(out readWriteValues, out writeOnlyValues); } /// <summary> /// 对xml中进行name和value的映射 /// </summary> /// <param name="readWriteValues"></param> /// <param name="writeOnlyValues"></param> /// <returns></returns> protected override IDictionary<XName, object> MapValues(IDictionary<XName, object> readWriteValues, IDictionary<XName, object> writeOnlyValues) { XName StatusXname = XName.Get("Status", propertiesNamespace); IDictionary<XName, object> mappedValues = base.MapValues(readWriteValues, writeOnlyValues); RequestForExpert requestForExpert = null; string Status = string.Empty; object value = null; //得到xml中Status的值(Status属性主要用来判断是否全部投票完毕,可以自行删除修改) if (writeOnlyValues.TryGetValue(StatusXname, out value)) { Status = (string)value; } //遍历workflow中所有的数据,直到找到保存的实体对象 foreach (KeyValuePair<System.Xml.Linq.XName, object> item in writeOnlyValues) { if (item.Value is LocationInfo) { LocationInfo li = (LocationInfo)item.Value; if (li.Value is RequestForExpert) { requestForExpert = (RequestForExpert)li.Value; } } } //确保存放数据信息的xml和存放文件的文件夹都存在 IOHelper.EnsureAllrfeFileExists(); // 加载存放数据信息的xml XElement doc = XElement.Load(IOHelper.GetAllrfesFileName()); //根据传递的参数Id找到xml节点 IEnumerable<XElement> current = from r in doc.Elements("requestForExpert") where r.Attribute("Id").Value.Equals(Id.ToString()) select r; //如果状态为关闭则删除掉xml中对应数据的节点 if (Status == "Closed") { foreach (XElement xe in current) { xe.Attribute("Status").Value = "finished"; } } else { foreach (XElement xe in current) { xe.Remove(); } if (requestForExpert != null) { XElement e = Serializerfe(requestForExpert); doc.Add(e); } } doc.Save(IOHelper.GetAllrfesFileName()); return mappedValues; } /// <summary> /// 序列化 /// </summary> /// <param name="rfe"></param> /// <returns></returns> XElement Serializerfe(RequestForExpert rfe) { XElement ret = new XElement("requestForExpert", new XAttribute("Id", rfe.ProjectId), new XAttribute("Status", rfe.Status)); //所有的评委列表节点 XElement expertList = new XElement("ExpertList"); foreach (ExpertInfo expert in rfe.ExpertList) { expertList.Add( new XElement("Expert", new XAttribute("UserId", expert.UserId), new XAttribute("UserName", expert.UserName), new XAttribute("IsConfirmed", expert.IsConfirmed.ToString())) ); } ret.Add(expertList); //推荐评委列表节点 XElement CandidateList = new XElement("CandidateList"); foreach (ExpertInfo expert in rfe.CandidateList) { CandidateList.Add( new XElement("Expert", new XAttribute("UserId", expert.UserId), new XAttribute("UserName", expert.UserName), new XAttribute("IsConfirmed", expert.IsConfirmed.ToString())) ); } ret.Add(CandidateList); //可选择评委列表节点 XElement OptionalList = new XElement("OptionalList"); foreach (ExpertInfo expert in rfe.OptionalList) { OptionalList.Add( new XElement("Expert", new XAttribute("UserId", expert.UserId), new XAttribute("UserName", expert.UserName), new XAttribute("IsConfirmed", expert.IsConfirmed.ToString())) ); } ret.Add(OptionalList); return ret; } /// <summary> ///传递所有加载的值(由 LoadWorkflowCommand 或 LoadWorkflowByInstanceKeyCommand 填充)作为字典参数 /// </summary> /// <param name="readWriteValues"></param> protected override void PublishValues(IDictionary<XName, object> readWriteValues) { base.PublishValues(readWriteValues); } }
XmlWorkflowInstanceStore 类主要重写了BeginCommand方法,通过判断命令来确定要执行的操作,分为创建 保存 加载三个类型,同时在保存的时候使用了Xml的方式,节点的名字可以自定义,但是要保证保存和加载时候使用相同的节点名称和属性。
XmlPersistenceParticipant 类主要是用来收集要保存的数据,往往是自定义的数据,比如自定义的实体或者自定义的其他复杂类型的数据,主要是MapValue映射方法,将要保存的实体都映射为一个个xml的节点和属性,这样就可以在下次Load的时候根据自己设置的id找到持久化的WorkFlow.
其中Serializerfe方法中的xml的节点大家是要自定义的,因为要根据自己要保存的对象的和属性来决定要保存哪些信息的,同时要考虑是否有集合属性等其他自己的复杂属性,这个在进行将Xml加载为对象的时候也要一一对应处理。
XML持久化实现之帮助类:
public static class RfeRepository { // id为实体类中的一个属性,也可以自定义 public static RequestForExpert Retrieve(Guid id) { XElement doc = XElement.Load(IOHelper.GetAllrfesFileName()); IEnumerable<RequestForExpert> current = from r in doc.Elements("requestForExpert") where r.Attribute("Id").Value.Equals(id.ToString()) select MapFrom(r); return current.First<RequestForExpert>(); } /// <summary> /// 获得当前项目(激活状态) /// </summary> /// <param name="id"></param> /// <returns></returns> public static RequestForExpert RetrieveActive(Guid id) { if (!File.Exists(IOHelper.GetAllrfesFileName())) return new RequestForExpert(); XElement doc = XElement.Load(IOHelper.GetAllrfesFileName()); IEnumerable<RequestForExpert> current = from rfe in doc.Descendants("requestForExpert") where (rfe.Attribute("Status").Value.Equals("active")) && (rfe.Attribute("Id").Value.Equals(id.ToString())) select MapFrom(rfe); return current.First<RequestForExpert>(); } /// <summary> /// 根据一个根节点,得到节点下的所有属性对应的节点 /// </summary> /// <param name="elem"></param> /// <returns></returns> static RequestForExpert MapFrom(XElement elem) { RequestForExpert rfe = new RequestForExpert(); rfe.ProjectId = new Guid(elem.Attribute("Id").Value); rfe.Status = elem.Attribute("Status").Value; //所有的评委 IEnumerable < ExpertInfo > expertList= elem.Descendants("ExpertList"). Descendants("Expert"). Select(x => new ExpertInfo { UserId = x.Attribute("UserId").Value, IsConfirmed = Convert.ToBoolean(x.Attribute("IsConfirmed").Value), UserName = x.Attribute("UserName").Value }); rfe.ExpertList = expertList.ToList(); //选中的评委 expertList = elem.Descendants("CandidateList"). Descendants("Expert"). Select(x => new ExpertInfo { UserId = x.Attribute("UserId").Value, IsConfirmed = Convert.ToBoolean(x.Attribute("IsConfirmed").Value), UserName = x.Attribute("UserName").Value }); rfe.CandidateList = expertList.ToList(); //可选择的评委 expertList = elem.Descendants("OptionalList"). Descendants("Expert"). Select(x => new ExpertInfo { UserId = x.Attribute("UserId").Value, IsConfirmed = Convert.ToBoolean(x.Attribute("IsConfirmed").Value), UserName = x.Attribute("UserName").Value }); rfe.OptionalList = expertList.ToList(); return rfe; } }
此类用于根据唯一标识去加载Xml中对应的实体对象。
public static class IOHelper { public static readonly string InstanceFormatString = "{0}.xml"; public static readonly string PersistenceDirectory = Path.Combine(Path.GetTempPath(), "FilePersistenceProvider"); public static string GetFileName(Guid id) { EnsurePersistenceFolderExists(); return Path.Combine(PersistenceDirectory, string.Format(CultureInfo.InvariantCulture, InstanceFormatString, id)); } public static string GetAllrfesFileName() { EnsurePersistenceFolderExists(); return Path.Combine(PersistenceDirectory, "rfes.xml"); } public static string GetTrackingFilePath(Guid instanceId) { EnsurePersistenceFolderExists(); return Path.Combine(PersistenceDirectory, instanceId.ToString() + ".tracking"); } public static void EnsurePersistenceFolderExists() { if (!Directory.Exists(PersistenceDirectory)) { Directory.CreateDirectory(PersistenceDirectory); } } public static void EnsureAllrfeFileExists() { string fileName = IOHelper.GetAllrfesFileName(); if (!File.Exists(fileName)) { XElement root = new XElement("requestForExperts"); root.Save(fileName); } } }
此类提供了Xml的路径查询以及确定Xml等文件存放的位置,默认存放在Temp临时文件夹下边,可以自定义修改,默认路径应该为 C:\Users\用户名\AppData\Local\Temp\FilePersistenceProvider下,每一个工作流有
2个xml,一个为业务数据对象的xml,一个为工作流对象的xml。
最后要修改一下,就是宿主的地方,看到了在调用持久化设置的时候传递的是Xml枚举值:
public WorkflowApplication CreateAndRun(RequestForExpert rfe) { IDictionary<string, object> inputs = new Dictionary<string, object>(); inputs.Add("InRequestForExpert", rfe); // 实例化工作流对象 Activity wf = new ExpertWorkFlow(); WorkflowApplication instance = new WorkflowApplication(wf, inputs); instance.PersistableIdle += OnIdleAndPersistable; instance.Completed += OnWorkflowCompleted; instance.Idle += OnIdle; //持久化设置 GetSqlInstanceStore(instance, StoreType.Xml); instance.Run(); return instance; }
public WorkflowApplication LoadInstance(Guid instanceId) { WorkflowApplication instance = new WorkflowApplication(new ExpertWorkFlow()); instance.Completed += OnWorkflowCompleted; instance.PersistableIdle += OnIdleAndPersistable; instance.Idle += OnIdle; //持久化设置 GetSqlInstanceStore(instance, StoreType.Xml); instance.Load(instanceId); return instance; }
好了,说了这么多,可能大家还不是太理解,大家可以对比源码来分析代码的用处,多提意见
下载源码:WF持久化操作