问题
I am working on implementing an update process for our large workflows that have the potential to last years, even decades. I'm picking up where previous developers have left off, and, after spending a week trying to get the existing code (based mostly off of Microsoft's provided example) to work, I've scrapped it and built my own WorkflowVersioning utility. While I've been able to successfully update persisted instances to new versions, in testing I've found that various changes can cause exceptions both during the update process as well as upon resuming affected workflows. Due to the complexity of our workflows, I believe that there is more work to be done, but do not currently have a plan of attack.
I've listed 2 specific issues I'm coming across, as well as the important bits of my code. If I need to provide other parts of the code please let me know. Additionally, if you yourself have implemented DynamicUpdate in a similar situation, or know of resources, code examples, or other documentation that may help, I am open to any help I can get with this! Being new to WF (and professional development in general), this has been quite the learning process, and I have struggled to find resources specific to WF4.5.
Background
The 3 main business processes we're currently developing consist of large flows, each with multiple child-flows, themselves built from many custom activities. The main Steps (we call them "Tasks") within each process are composed of a NativeActivity, as well as several CodeActivities dragged onto the Xaml design surface. (I'm also having issues with getting the designer to behave, but I'll save that for a separate question).
Specific Problems
1) When making changes to the flow, if I add or remove arguments I get an exception when applying the update:
Exception thrown: 'System.Activities.DynamicUpdate.InstanceUpdateException' in System.Activities.dll ("In order for an implementation map to be directly applied to a workflow instance, the implementation map must indicate that there is no change to arguments. The implementation map indicates that arguments of the activity definition have changed."
According to MSDN (wouldn't let me post a third link, i'll add it in comments), you should be able to add and remove arguments: is there further code needed to account for this? Google results have been thin to say the least. However, when analyzing the generated DynamicUpdateMap, I see that it does track New and Old arguments, so I'm assuming there must be a way!
2) When I added a variable to the main flow, I got an exception upon resumption of a task:
Message=Unable to locate the ICompiledExpressionRoot for compiled location 'flowData'. Make sure that the definition for the activity containing this expression has been compiled.
The only result I could find was this post about IIS, but why would this error occur when I'm simply running a desktop application from Visual Studio? I can't seem to find any other results to point me in a direction on this one..
WorkflowVersioning Utility Project
Disclaimer: Much of this code is thanks to Matt Milner's WF4.5 pluralsight course, one of the few (somewhat current) resources I could find on the subject. I would love to see more examples of the DynamicUpdate process in action, but for now:
Preparing flows for update
First I get an ActivityBuilder from the Main Process's (Parent Workflow) .xaml:
private static ActivityBuilder GetBuilderFromFile(string sourcePath)
{
// Create the XamlXmlReaderSettings.
Console.WriteLine("Creating Xaml Reader...");
XamlXmlReaderSettings readerSettings = new XamlXmlReaderSettings()
{
LocalAssembly = Assembly.LoadFile(
Path.GetFullPath(assemblyVersionsPath))
};
XamlXmlReader xamlReader = new XamlXmlReader(sourcePath, readerSettings);
ActivityBuilder activity = XamlServices.Load(ActivityXamlServices.CreateBuilderReader(xamlReader)) as ActivityBuilder;
return activity;
}
I then call PrepareForUpdate:
DynamicUpdateServices.PrepareForUpdate(activityBuilder);
And then save the ActivityBuilder back to file, with the now-added DynamicUpdate metadata.
private static void SaveBuilderToFile(ActivityBuilder builder, string filePath)
{
// Make sure the activity builder has the right information about the c# expressions (even though its visual basic)
VisualBasic.SetSettings(builder, VisualBasic.GetSettings(builder));
// Set c# as the language
System.Activities.Presentation.Expressions.ExpressionActivityEditor.SetExpressionActivityEditor(builder, "C#");
WorkflowViewState.SetViewStateManager(
builder.Implementation, WorkflowViewState.GetViewStateManager(builder.Implementation));
string fullPath = Path.GetFullPath(filePath);
using (FileStream file = File.Create(fullPath))
{
using (XmlWriter xmlWriter = XmlWriter.Create(file, new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }))
{
using (XamlWriter xamlWriter = ActivityXamlServices.CreateBuilderWriter( new XamlXmlWriter(xmlWriter, new XamlSchemaContext())))
{
XamlServices.Save(xamlWriter, builder);
}
}
}
}
After performing the prepare for update process, I make changes to the workflow using the designer or modifying NativeActivities by adding/removing arguments/variables/activities.
Creating a DynamicUpdateMap
After making any required changes to the workflow, I generate a dynamic update map:
public static void CreateUpdateMap(string processToMap, string sourceActivityXaml, Version targetIdentityVersion)
{
ActivityBuilder builder = GetBuilderFromFile(sourceActivityXaml);
DynamicUpdateMap map = DynamicUpdateServices.CreateUpdateMap(builder);
// save the update map
string updateDirectory = @"..\..\UpdateMaps";
string fileName = Path.Combine(updateDirectory, processToMap + ".map");
// ToDo: If a .map already exists here, either delete or rename it, or prompt the user to decide. Otherwise it mixes with the new one.
using (FileStream fileStream = File.OpenWrite(fileName))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(DynamicUpdateMap));
serializer.WriteObject(fileStream, map);
}
// save builder back to source file, with the dynamicupdate info removed.
SaveBuilderToFile(builder, sourceActivityXaml);
}
Apply the update map to affected instances
Finally, I iterate through each instance in persistence, and apply the update:
foreach (Guid id in GetIds())
{
// Get a proxy to the instance
WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(id, store);
Console.WriteLine("Inspecting: {0}", instance.DefinitionIdentity);
if (instance.DefinitionIdentity != null &&
instance.DefinitionIdentity.Name == processToUpdate.ToString() &&
instance.DefinitionIdentity.Version.Equals(previousVersion))
{
Tuple<WorkflowIdentity, DynamicUpdateMap> mapInfo = manager.GetMap(instance.DefinitionIdentity);
DynamicUpdateMap map = mapInfo.Item2;
WorkflowIdentity newIdentity = mapInfo.Item1;
// get the application, loading, and applying the map
//WorkflowApplication wfApp = GetWorkflowApplication( manager[newIdentity], instance, newIdentity, map);
Activity wf = manager[newIdentity];
WorkflowApplication wfApp = new WorkflowApplication(wf, newIdentity);
wfApp.Load(instance, map);
wfApp.Unload();
}
else
{
instance.Abandon();
Console.WriteLine("Instance {0} is not updatable", instance.InstanceId);
}
}
来源:https://stackoverflow.com/questions/41069180/wf4-5-dynamic-update-issues-when-updating-large-long-running-flows-with-custo