If you have instance data coming from the tracking database and want to get the actual instance activity tree after the changes were applied, it gets a little tricky.

First, you’d need to

a)      read all of the activity add/remove changes from the database and merge them into single list

b)      order them correctly

 

To complicate the matters a little bit, the changes are split into two tables, AddedActivity and RemovedActivity. The app uses two stored procs, [dbo].[GetAddActivityActionsForWorkflowInstance] and [dbo].[GetRemovedActivityActionsForWorkflowInstance] to read those.

 

Every time workflow changes are applied, they are tracked as a single instance event.

 

But since there may be multiple activities added/removed in a single tracked change, they need to be applied in the order they were made during every change. Let’s say that during single workflow change session you first added a parallel activity, then removed one of the branches and then dropped a code activity into the other branch. Now, if you apply those the changes in a wrong order, you’d simply fail.

 

That’s why after added/removed changes are read, they need to be merged into a single list and then sorted by the two properties, WorkflowInstanceEventId and Order.

 

Here is a little neat class that does the sorting (which got enabled by the new Whidbey List<> class):

 

private sealed class TrackedChangeActionSorter : IComparer<TrackedChangeAction>

{

      public static void Sort(List<TrackedChangeAction> actions)

      {

            TrackedChangeActionSorter sorter = new TrackedChangeActionSorter();

            actions.Sort(sorter);

      }

 

      int IComparer<TrackedChangeAction>.Compare(TrackedChangeAction lhs, TrackedChangeAction rhs)

      {

            if (lhs.WorkflowInstanceEventId < rhs.WorkflowInstanceEventId)

                  return -1;

            else if (lhs.WorkflowInstanceEventId == rhs.WorkflowInstanceEventId)

            {

                  if (lhs.Order < rhs.Order)

                        return -1;

                  else if (lhs.Order == rhs.Order)

                        return 0;

                  else

                        return +1;

            }

            else

                  return +1;

      }

}

 

Now that you have the changes in the right order, they need to be applied to the original activity tree. As you may have noticed, some of the changes have non-null AddedActivityAction or RemovedActivityAction properties. If not null, they hold markup-serialized instances of ActivityChangeAction–derived class (either AddedActivityAction or RemovedActivityAction).

Once you got hold of those, you may deserialize and apply them (in the correctly sorted order) to the actual activity tree. The method we need is unfortunately marked protected in WF v1 so we’d need to use reflection to call it:

 

CompositeActivity rootActivity = ...;

WorkflowChangeAction[] changes = ...;

 

if (rootActivity != null && changes != null && changes.Length > 0)

{

      for (int i = 0; i < changes.Length; i++)

      {

            WorkflowChangeAction changeAction = changes[i];

            if(changeAction != null)

            {

                  //protected abstract bool ApplyTo(Activity rootActivity);

                  MethodInfo applyMethod = changeAction.GetType().GetMethod("ApplyTo", BindingFlags.Instance | BindingFlags.NonPublic);

                  applyMethod.Invoke(changeAction, new object[] { rootActivity });

            }

      }

}

 

Another neat class I wanted to point out is the ActivityMarkupWithChanges. In v1 we have a limitation in the serializer that doesn’t allow serializing changed custom activity/workflow types with children activities in them (see issue number one in the second part of this article). The mentioned class was created to overcome that limitation. What it does it serializes list of the applied changes (stored in a form of WorkflowChangeAction-derived instances) along with the original workflow:

 

[Serializable]

public class ActivityMarkupWithChanges

{

      private Activity root = null;

      private WorkflowChangeAction[] changes = null;

 

      public ActivityMarkupWithChanges()

      {

      }

 

      public ActivityMarkupWithChanges(Activity root, WorkflowChangeAction[] changes)

      {

            this.root = root;

            this.changes = changes;

      }

 

      public Activity Root

      {

            get { return this.root; }

            set { this.root = value; }

      }

      public WorkflowChangeAction[] Changes

      {

            get { return this.changes; }

            set { this.changes = value; }

      }

}

 

As a matter of fact the code snipped above with an example of ApplyTo usage is exactly what the protected override void PerformLoad(IDesignerSerializationManager iSerializationManager) does after it deserializes an instance of ActivityMarkupWithChanges with non-empty list of changes.