在上一篇中提到了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持久化操作