locked
custom sql persistence RRS feed

  • Question

  • What I'm basically doing is deriving from the SqlWorkflowPersistenceService:

    public class SqlWorkflowStepPersistenceService : SqlWorkflowPersistenceService {
    protected override System.Workflow.ComponentModel.Activity LoadWorkflowInstanceState(Guid id)
            {
                Guid StepID;
                byte[] State;
                if (RetreiveNextStepData(id, out StepID, out State))
                    return WorkflowPersistenceService.RestoreFromDefaultSerializedForm(State, null);
                else return base.LoadWorkflowInstanceState(id);
            }


            public void RegisterProcess(Guid ProcessID)
            {
                SqlCommand RegisterCmd = new SqlCommand("InsertProcess", _SqlConnection);
                RegisterCmd.CommandType = CommandType.StoredProcedure;
                RegisterCmd.Parameters.Add("@ProcessID", SqlDbType.UniqueIdentifier).Value = ProcessID;
                _SqlConnection.Open();
                try
                {
                    RegisterCmd.ExecuteNonQuery();
                }
                finally
                {
                    _SqlConnection.Close();
                }
            }

            public void PersistStep(Guid ProcessId, Guid StepId, string StepName, System.Workflow.ComponentModel.Activity RootActivity)
            {
                byte[] serializedActivity = WorkflowPersistenceService.GetDefaultSerializedForm(RootActivity);
                SqlCommand PersistCmd = new SqlCommand("InsertProcessStep", _SqlConnection);
                PersistCmd.CommandType = CommandType.StoredProcedure;
                PersistCmd.Parameters.Add("@ProcessID", SqlDbType.UniqueIdentifier).Value = ProcessId;
                PersistCmd.Parameters.Add("@StepID", SqlDbType.UniqueIdentifier).Value = StepId;
                PersistCmd.Parameters.Add("@StepName", SqlDbType.NVarChar, 255).Value = StepName;
                PersistCmd.Parameters.Add("@State", SqlDbType.Image).Value = serializedActivity;
                _SqlConnection.Open();
                try
                {
                    PersistCmd.ExecuteNonQuery();
                }
                finally
                {
                    _SqlConnection.Close();
                }
            }

            public void ForceRollback(Guid ProcessID, Guid StepID)
            {
                SqlCommand RollbackCmd = new SqlCommand("ForceRollBack", _SqlConnection);
                RollbackCmd.CommandType = CommandType.StoredProcedure;
                RollbackCmd.Parameters.Add("@ProcessID", SqlDbType.UniqueIdentifier).Value = ProcessID;
                RollbackCmd.Parameters.Add("@StepID", SqlDbType.UniqueIdentifier).Value = StepID;
                _SqlConnection.Open();
                try
                {
                    RollbackCmd.ExecuteNonQuery();
                }
                finally
                {
                    _SqlConnection.Close();
                }  
            }

            public bool RetreiveNextStepData(Guid ProcessID, out Guid StepID, out byte[] State)
            {
                SqlCommand RetreiveCmd = new SqlCommand("RetreiveNextStepData", _SqlConnection);
                RetreiveCmd.CommandType = CommandType.StoredProcedure;
                RetreiveCmd.Parameters.Add("@ProcessID", SqlDbType.UniqueIdentifier).Value = ProcessID;

                SqlParameter StepIDParameter = RetreiveCmd.CreateParameter();
                StepIDParameter.ParameterName = "@StepID";
                StepIDParameter.Direction = ParameterDirection.Output;
                StepIDParameter.SqlDbType = SqlDbType.UniqueIdentifier;
                RetreiveCmd.Parameters.Add(StepIDParameter);

                _SqlConnection.Open();
                try
                {
                    State = (byte[])RetreiveCmd.ExecuteScalar();
                }
                finally
                {
                    _SqlConnection.Close();
                }

                if (StepIDParameter.Value == null || StepIDParameter.Value == DBNull.Value) {
                    StepID = Guid.Empty;
                    return false;
                }
                StepID = (Guid)StepIDParameter.Value;
                return StepID!=Guid.Empty;
            }
    }

    What I want for my sequential workflow is to be able to return execution back to some activity. In the workflow I have several custom activities that represent the steps I would like to move back in case I need it.
    I'm calling my PersistStep method from the workflow and persist the whole workflow in my custom database registering each step.
    Then I call:
    workflowInstance.Unload(); //to unload through the default
                                                 //persistence service
    workflowInstance.Load(); //to load using my implementation

    and my persistence service executes and seems to return the right step data (I checked it), but the workflow seems to be in its starting state executing from the very first activity instead of the back step where I wanted it to go.

    Do I miss something in the scenario or how its because my custom activities don't persist the right way. So, how do I make my workflow execute from that "back" step as I just loaded it from the database.
    Thursday, October 19, 2006 3:44 PM

Answers

  • I believe it to be true that you cannot control the children of a composite from outside the composite without some prior coding within the composite to allow it. 

    However, I can think of two options that might help you out.  One option would be to look at using Dynamic Update here to change the composite before using it.  This would let you add/remove activities from the If/Else for example and then execute it.  But I'm not sure it will work in your case based on how you want to control things.  If you are writing your own custom parent, then it might just work. 

    The other is using rules and conditions to dictate what runs and what doesn't.  Again, see my sample (link in previous post) for a sequence that would allow this.  You could put this activity in the If/Else or Parallel branches and then use rules to drive what activities execute.  This bases it on conditions and state rather than you having to directly control the activities.  You'll need to make sure your rules are written in such a way as to respect the execution contexts and still need a parent/wrapping activity that goes back and creates a new context in which to run the If/Else or Parallel for example. 

    Otherwise, yes,  I think you would have to write your own versions of those activities if you want to change their execution semantics. 

    On the second item, it depends on if you own the execution logic of the activity.  If you do, then setting properties or using rules can easily allow you to change the logic.  If you don't own the exeuction logic (i.e. with the built in activities) then you have the options above or any that the activity itself allows. 

    matt

     

    Monday, October 23, 2006 4:21 PM

All replies

  • One more thing to add -  when I get my workflow from the persistence store it's in the right state, but it just doesnt execute from the step where I persisted it. It executes from the start.
    Friday, October 20, 2006 7:58 AM
  • >> What I want for my sequential workflow is to be able to return execution back to some activity. In the workflow I have several custom activities that represent the steps I would like to move back in case I need it.

    You can't move the execution context of an instance.  Attempting to change the state of the workflow via persistence is not supported.  If you need to return to a previous point in the workflow you should code that into the workflow's logic.

    Thanks,
    Joel West
    MSFTE - SDE in WF runtime and hosting

    This posting is provided "AS IS" with no warranties, and confers no rights

    Friday, October 20, 2006 4:25 PM
  • Hey, thanks for replying. That was exactly what I did when I first wanted custom behaviour for my workflow. I implemented my custom workflow, that was kind of a sequential workflow but it also could step back or execute from a previous activity. Also, I had custom activities that represented the steps I could move back.  Unfortunately things get much more complex when my custom activities in the workflow are placed inside ifelse activites or any other composites, making the workflow execute from those steps inside of some composite activities (for any case designed) might lead to unexpected behaviour. Besides that, the parent custom workflow that I designed only calls the execute method of the activities inside. That's why I cannot tell, for instance to an ifelse activity to execute some specific activity inside it, I just can call its Execute. If I would want custom execution for the standard activities I'd have to rewrite any of them so that they support my "back" functionality. Not too smart ...

    So I've tried using persistence in order to store the workflow in the state it was and restore that state when I wanted the workflow to execute from some point back. If attempting to change the state of the workflow via persistence is not supported this approach makes no sense. Could you suggest any other way to implement the "back functionality" and be able to use the standard activities in the workflow.

    Thanks,

    Slavik Ceornea

    MCTS

    Saturday, October 21, 2006 11:24 AM
  • You might try writing a custom composite activity that allows you to control execution the way you want.  For example, create a custom activity that derives from sequence, but allows you to set properties on it to indicate that it should return and begin executing at a previous child activity. 

    The important things to consider are:

    1. When you go back and start to run activities again, use a new activity execution context.  This will work much like a the while activity to let you go back and run activities.  The new execution context is necessary to run those activities that have closed again. 
    2. Register for the closed event of your children and in the event handler, check the property to see if you should go back and start executing at an earlier step, or continue on with the next activity. 

    I have a sample of a composite activity that derives from sequence.  It doesn't do exactly what you want, but has many of the concepts.  You can check it out on the WF community site. 

    http://wf.netfx3.com/files/folders/control_flow/entry3469.aspx

    HTH,

    Matt

    Saturday, October 21, 2006 7:27 PM
  • Thanks for replying, Matt. That's exactly what I was trying at the very beginning. I implemented a custom root activity that provided the behavior that I needed. The custom activity was smth like this, I cut some code to make it simple:

    public partial class ControlWorkflowActivity : CompositeActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>

    {

    protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)

    {

    ControlStack = new List<ControlStackItem>();

    Activity firstChildActivity = EnabledActivities[0];

    firstChildActivity.RegisterForStatusChange(Activity.ClosedEvent, this);

    executionContext.ExecuteActivity(firstChildActivity);

    //setting current activity index accessible outside of ControlWorkflowActivity

    CurrentCtrlActivityIndex = executeCtrlActivityIndex;

    SetCurrentStackIndex();

    executeCtrlActivityIndex++;

    return ActivityExecutionStatus.Executing;

    }

     

    public void OnEvent(object sender, ActivityExecutionStatusChangedEventArgs e)

    {

    ActivityExecutionContext executionContext = sender as ActivityExecutionContext;

    if (executionContext == null)

    throw new ApplicationException("Activity execution context cannot be null.");

    e.Activity.UnregisterForStatusChange(Activity.ClosedEvent, this);

    if (this.ParentEventArgs.FlowMessage.Direction == ControlFlowMessage.CommandEnum.Back)

    {

    //sets a fallback value on executeCtrlActivityIndex;

    SetFallbackIndex();

    }

    //creating next activity context

    ActivityExecutionContext childExecutionContext =

    executionContext.ExecutionContextManager.CreateExecutionContext(EnabledActivities[executeCtrlActivityIndex]);

    //setting current activity index accessible outside of ControlWorkflowActivity

    CurrentCtrlActivityIndex = executeCtrlActivityIndex;

    SetCurrentStackIndex();

    childExecutionContext.Activity.RegisterForStatusChange(Activity.ClosedEvent, this);

    childExecutionContext.ExecuteActivity(childExecutionContext.Activity);

    executeCtrlActivityIndex++;

    }

    }

    I add other custom activities that represent my steps into this workflow and run them again if I need to execute from a step back. Now imagine I have some composite activities in my custom workflow (several ifelse activities) that contain my custom 'step' activities. My root activity calls the Execute on the ifelse activities, but if I would like to move back inside of those ifelse activities my root activity has no control over that. That's why I was trying persistence to find out if I could persist the state of the workflow at a point, then bring my workflow to that state later.

    Joel wrote persistence is not supposed to work in this scenario and I don't quite understand why persistence cannot make my workflow be loaded into a previous state. And if persistence is inconvenient in this case, how do I make my root custom workflow handle "back execution" into composite activities declared into it?

    Thanks

    Slava

    Monday, October 23, 2006 9:31 AM
  • Persistence is about going back to a known good point in a failure and executing activities which did not have their results persisted.  What you want to do is go back and REPEAT some activities and persistence is not about that. 

    You are correct that you have hit a bit of a roadblock that keeps you from going into the child activities of a composite as that composite itself is responsible for their execution.  If you cannot control the composite (i.e. an if/else) then you cannot control reexecuting only part of its children (unless you change the condition, but that only gives you specific limited control). 

    If you own the composite, then you could use a custom base class that allowed you to control which activities to execute (much like the sample I mentioned). 

    The long and short is that if you need to control which children execute, you need to own the composite or be able to set a condition on it to reexecute it.  You might look at the CAG which is way overkill for normal if/else, but might help you do what you want.  Otherwise, you have some custom activities that you'll have to write I think. 

    Matt

     

    Monday, October 23, 2006 3:19 PM
  • Thanks Matt, but just to make things clear, you mean there's no other way to control the execution of a composite activity from its root parent than just implementing new activities supporting the "back functionality"? Does this mean I have to rewrite any standard composite activity I would like to use in my specialized workflow (ifelse, parallel or other standard composites) to make any scenario work?

    And one more thing, is it possible to influence the execution of composites from the root activity through other mechanisms than dependency properties attached to children?

    Slava

    Monday, October 23, 2006 4:09 PM
  • I believe it to be true that you cannot control the children of a composite from outside the composite without some prior coding within the composite to allow it. 

    However, I can think of two options that might help you out.  One option would be to look at using Dynamic Update here to change the composite before using it.  This would let you add/remove activities from the If/Else for example and then execute it.  But I'm not sure it will work in your case based on how you want to control things.  If you are writing your own custom parent, then it might just work. 

    The other is using rules and conditions to dictate what runs and what doesn't.  Again, see my sample (link in previous post) for a sequence that would allow this.  You could put this activity in the If/Else or Parallel branches and then use rules to drive what activities execute.  This bases it on conditions and state rather than you having to directly control the activities.  You'll need to make sure your rules are written in such a way as to respect the execution contexts and still need a parent/wrapping activity that goes back and creates a new context in which to run the If/Else or Parallel for example. 

    Otherwise, yes,  I think you would have to write your own versions of those activities if you want to change their execution semantics. 

    On the second item, it depends on if you own the execution logic of the activity.  If you do, then setting properties or using rules can easily allow you to change the logic.  If you don't own the exeuction logic (i.e. with the built in activities) then you have the options above or any that the activity itself allows. 

    matt

     

    Monday, October 23, 2006 4:21 PM