Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
.NET Framework 4.6.1 bevat SqlWorkflowInstanceStoreeen exemplaararchief dat gebruikmaakt van SQL Server om werkstroomgegevens te behouden. Als uw toepassing werkstroomgegevens naar een ander medium moet bewaren, zoals een andere database of een bestandssysteem, kunt u een aangepast exemplaararchief implementeren. Er wordt een aangepast exemplaararchief gemaakt door de abstracte InstanceStore klasse uit te breiden en de methoden te implementeren die vereist zijn voor de implementatie. Voor een volledige implementatie van een aangepaste instantie-opslag, zie het voorbeeld van het bedrijfsaankoopproces.
De methode BeginTryCommand implementeren
De BeginTryCommand wordt door de persistentie-engine naar de exemplaaropslag verzonden. Het type van de command parameter geeft aan welke opdracht wordt uitgevoerd. Deze parameter kan van de volgende typen zijn:
SaveWorkflowCommand: De persistentie-engine verzendt deze opdracht naar de instantieopslag wanneer een werkstroom moet worden opgeslagen op het opslagmedium. De persistentiegegevens van de werkstroom worden verstrekt aan de methode in het InstanceData lid van de
commandparameter.LoadWorkflowCommand: De persistentie-engine verzendt deze opdracht naar de instantieopslag als een werkstroom moet worden geladen vanaf het opslagmedium. De instantie-id van de werkstroom die moet worden geladen, wordt geleverd aan de methode in de
instanceIdparameter van de InstanceView eigenschap van decontextparameter.CreateWorkflowOwnerCommand: De persistentie-engine verzendt deze opdracht naar het exemplaararchief wanneer een WorkflowServiceHost moet worden geregistreerd als eigenaar van een vergrendeling. De exemplaar-id van de huidige werkstroom moet worden opgegeven in het exemplaararchief met behulp BindInstanceOwner van de methode van de
contextparameter.Het volgende codefragment laat zien hoe u de opdracht CreateWorkflowOwner implementeert om een expliciete vergrendelingseigenaar toe te wijzen.
XName WFInstanceScopeName = XName.Get(scopeName, "<namespace>"); ... CreateWorkflowOwnerCommand createCommand = new CreateWorkflowOwnerCommand() { InstanceOwnerMetadata = { { WorkflowHostTypeName, new InstanceValue(WFInstanceScopeName) }, } }; InstanceHandle ownerHandle = store.CreateInstanceHandle(); store.DefaultInstanceOwner = store.Execute( ownerHandle, createCommand, TimeSpan.FromSeconds(30)).InstanceOwner; childInstance.AddInitialInstanceValues(new Dictionary<XName, object>() { { WorkflowHostTypeName, WFInstanceScopeName } });DeleteWorkflowOwnerCommand: De persistentie-engine verzendt dit commando naar de instance store wanneer de instance ID van een vergrendelingseigenaar uit de instance store kan worden verwijderd. Net als bij CreateWorkflowOwnerCommand, moet de toepassing de ID van de eigenaar van de vergrendeling opgeven.
In het volgende codefragment ziet u hoe u een vergrendeling kunt vrijgeven met behulp van DeleteWorkflowOwnerCommand.
static void FreeHandleAndDeleteOwner(InstanceStore store, InstanceHandle handle) { handle.Free(); handle = store.CreateInstanceHandle(store.DefaultInstanceOwner); try { // We need this sleep so that we dont prematurely delete the owner, we need time to update the database. Thread.Sleep(10000); store.Execute(handle, new DeleteWorkflowOwnerCommand(), TimeSpan.FromSeconds(10)); } catch (InstancePersistenceCommandException ex) { Log.Inform(ex.Message); } catch (InstanceOwnerException ex) { Log.Inform(ex.Message); } catch (OperationCanceledException ex) { Log.Inform(ex.Message); } catch (Exception ex) { Log.Inform(ex.Message); } finally { handle.Free(); store.DefaultInstanceOwner = null; } }De bovenstaande methode moet worden aangeroepen in een Try/Catch-blok wanneer een child-exemplaar wordt uitgevoerd.
try { childInstance.Run(); } catch (Exception) { throw ; } finally { FreeHandleAndDeleteOwner(store, ownerHandle); }LoadWorkflowByInstanceKeyCommand: De persistentie-engine stuurt deze opdracht naar de exemplaaropslag wanneer een werkstroomexemplaar moet worden geladen met behulp van de instancesleutel van het werkstroomexemplaar. De id van de exemplaarsleutel kan worden bepaald met behulp van de LookupInstanceKey parameter van de opdracht.
QueryActivatableWorkflowsCommand: De persistentie-engine verzendt deze opdracht naar de exemplaaropslag om activatieparameters van gepersisteerde werkstromen op te halen, zodat een werkstroomhost kan worden gemaakt die vervolgens de werkstromen kan laden. Deze opdracht wordt door de engine verzonden als reactie op de instantiewinkel die een signaal naar de host stuurt wanneer het een exemplaar vindt dat geactiveerd kan worden. De instance store moet worden gepeild om te bepalen of er werkstromen zijn die geactiveerd kunnen worden.
TryLoadRunnableWorkflowCommand: De persistentie-engine verzendt deze opdracht naar de exemplaaropslag om uitvoerbare werkstroomexemplaren te laden. Deze opdracht wordt door de engine verzonden als reactie op het exemplaararchief dat de HasRunnableWorkflowEvent naar de host verhoogt wanneer er een exemplaar wordt gevonden dat kan worden uitgevoerd. De instance-opslag moet werkstromen pollen die uitgevoerd kunnen worden. In het volgende codefragment ziet u hoe u een exemplaararchief kunt peilen voor werkstromen die kunnen worden uitgevoerd of geactiveerd.
public void PollForEvents() { InstanceOwner[] storeOwners = this.GetInstanceOwners(); foreach (InstanceOwner owner in storeOwners) { foreach (InstancePersistenceEvent ppEvent in this.GetEvents(owner)) { if (ppEvent.Equals(HasRunnableWorkflowEvent.Value)) { bool hasRunnable = GetRunnableEvents(this.StoreId, owner.InstanceOwnerId, isActivatable); if (hasRunnable) { this.SignalEvent(ppEvent, owner); } else { this.ResetEvent(ppEvent, owner); } } else if(ppEvent.Equals(HasActivatableWorkflowEvent.Value)) { bool hasActivatable = GetActivatableEvents(this.StoreId, owner.InstanceOwnerId); if (hasActivatable) { this.SignalEvent(ppEvent, owner); } else { this.ResetEvent(ppEvent, owner); } } } } }In het bovenstaande codefragment voert de instance store een query uit op de beschikbare gebeurtenissen en onderzoekt elke gebeurtenis om te bepalen of het een HasRunnableWorkflowEvent gebeurtenis is. Als er een wordt gevonden, wordt SignalEvent aangeroepen om de host te signaleren een opdracht naar de instantieopslag te verzenden. In het volgende codefragment ziet u een implementatie van een handler voor deze opdracht.
If (command is TryLoadRunnableWorkflowCommand) { Owner owner; CheckOwner(context, command.Name, out owner); // Checking instance.Owner is like an InstanceLockQueryResult. context.QueriedInstanceStore(new InstanceLockQueryResult(context.InstanceView.InstanceId, context.InstanceView.InstanceOwner.InstanceOwnerId)); XName ownerService = null; InstanceValue value; Instance runnableInstance = default(Instance); bool foundRunnableInstance = false; value = QueryPropertyBag(WorkflowNamespace.WorkflowHostType, owner.Data); if (value != null && value.Value is XName) { ownerService = (XName)value.Value; } foreach (KeyValuePair<Guid, Instance> instance in MemoryStore.instances) { if (instance.Value.Owner != Guid.Empty || instance.Value.Completed) { continue; } if (ownerService != null) { value = QueryPropertyBag(WorkflowNamespace.WorkflowHostType, instance.Value.Metadata); if (value == null || ((XName)value.Value) != ownerService) { continue; } } value = QueryPropertyBag(WorkflowServiceNamespace.SuspendReason, instance.Value.Metadata); if (value != null && value.Value != null && value.Value is string) { continue; } value = QueryPropertyBag(WorkflowNamespace.Status, instance.Value.Data); if (value != null && value.Value is string && ((string)value.Value) == "Executing") { runnableInstance = instance.Value; foundRunnableInstance = true; } if (!foundRunnableInstance) { value = QueryPropertyBag(XNamespace.Get("urn:schemas-microsoft-com:System.Activities/4.0/properties").GetName("TimerExpirationTime"), instance.Value.Data); if (value != null && value.Value is DateTime && ((DateTime)value.Value) <= DateTime.UtcNow) { runnableInstance = instance.Value; foundRunnableInstance = true; } } if (foundRunnableInstance) { runnableInstance.LockVersion++; runnableInstance.Owner = context.InstanceView.InstanceOwner.InstanceOwnerId; MemoryStore.instances[instance.Key] = runnableInstance; context.BindInstance(instance.Key); context.BindAcquiredLock(runnableInstance.LockVersion); Dictionary<Guid, IDictionary<XName, InstanceValue>> associatedKeys = new Dictionary<Guid, IDictionary<XName, InstanceValue>>(); Dictionary<Guid, IDictionary<XName, InstanceValue>> completedKeys = new Dictionary<Guid, IDictionary<XName, InstanceValue>>(); foreach (KeyValuePair<Guid, Key> keyEntry in MemoryStore.keys) { if (keyEntry.Value.Instance == context.InstanceView.InstanceId) { if (keyEntry.Value.Completed) { completedKeys.Add(keyEntry.Key, DeserializePropertyBag(keyEntry.Value.Metadata)); } else { associatedKeys.Add(keyEntry.Key, DeserializePropertyBag(keyEntry.Value.Metadata)); } } } context.LoadedInstance(InstanceState.Initialized, DeserializePropertyBag(runnableInstance.Data), DeserializePropertyBag(runnableInstance.Metadata), associatedKeys, completedKeys); break; } }In het bovenstaande codefragment zoekt de instance store naar uitvoerbare instanties. Als er een instantie wordt gevonden, wordt deze aan de uitvoeringscontext gebonden en geladen.
Een aangepaste instantie-opslag gebruiken
Als u een aangepast exemplaararchief wilt implementeren, wijst u een exemplaar van het exemplaararchief toe aan de InstanceStore en implementeert u de PersistableIdle methode. Zie de zelfstudie : Een langlopende werkstroom maken en uitvoeren voor specifieke informatie.
Een voorbeeldexemplarenarchief
Het volgende codevoorbeeld is een complete instance store-implementatie, afkomstig uit het bedrijfsaankoopproces voorbeeld. In deze instance-opslag worden werkstroomgegevens met behulp van XML in een bestand bewaard.
using System;
using System.Activities.DurableInstancing;
using System.Collections.Generic;
using System.IO;
using System.Runtime.DurableInstancing;
using System.Runtime.Serialization;
using System.ServiceModel.Persistence;
using System.Xml;
using System.Xml.Linq;
namespace Microsoft.Samples.WF.PurchaseProcess
{
public class XmlWorkflowInstanceStore : InstanceStore
{
Guid ownerInstanceID;
public XmlWorkflowInstanceStore() : this(Guid.NewGuid())
{
}
public XmlWorkflowInstanceStore(Guid id)
{
ownerInstanceID = id;
}
//Synchronous version of the Begin/EndTryCommand functions
protected override bool TryCommand(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout)
{
return EndTryCommand(BeginTryCommand(context, command, timeout, null, null));
}
//The persistence engine will send a variety of commands to the configured InstanceStore,
//such as CreateWorkflowOwnerCommand, SaveWorkflowCommand, and LoadWorkflowCommand.
//This method is where we will handle those commands
protected override IAsyncResult BeginTryCommand(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)
{
IDictionary<XName, InstanceValue> data = null;
//The CreateWorkflowOwner command instructs the instance store to create a new instance owner bound to the instanace handle
if (command is CreateWorkflowOwnerCommand)
{
context.BindInstanceOwner(ownerInstanceID, Guid.NewGuid());
}
//The SaveWorkflow command instructs the instance store to modify the instance bound to the instance handle or an instance key
else if (command is SaveWorkflowCommand)
{
SaveWorkflowCommand saveCommand = (SaveWorkflowCommand)command;
data = saveCommand.InstanceData;
Save(data);
}
//The LoadWorkflow command instructs the instance store to lock and load the instance bound to the identifier in the instance handle
else if (command is LoadWorkflowCommand)
{
string fileName = IOHelper.GetFileName(this.ownerInstanceID);
try
{
using (FileStream inputStream = new FileStream(fileName, FileMode.Open))
{
data = LoadInstanceDataFromFile(inputStream);
//load the data into the persistence Context
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);
}
//Reads data from xml file and creates a dictionary based off of that.
IDictionary<XName, InstanceValue> LoadInstanceDataFromFile(Stream inputStream)
{
IDictionary<XName, InstanceValue> data = new Dictionary<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");
XName key = (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;
}
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;
}
//Saves the persistence data to an xml file.
void Save(IDictionary<XName, InstanceValue> instanceData)
{
string fileName = IOHelper.GetFileName(this.ownerInstanceID);
XmlDocument doc = new XmlDocument();
doc.LoadXml("<InstanceValues/>");
foreach (KeyValuePair<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);
}
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;
}
}
}