ObjectDisposedException from ActivityContext

คำตอบที่เสนอ ObjectDisposedException from ActivityContext

  • Friday, January 29, 2010 11:26 PM
     
     
    Hi All,

    I am getting this error:

    System.ObjectDisposedException: An ActivityContext can only be accessed within the scope of the function it was passed into.
    Object name: 'System.Activities.ActivityContext'.
       at System.Activities.ActivityContext.ThrowIfDisposed()
       at System.Activities.ActivityContext.GetValue[T](LocationReference locationReference)
       at lambda_method(Closure , Resource )
       at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
       at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
       at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
       at System.Data.Linq.Table`1.DeleteAllOnSubmit[TSubEntity](IEnumerable`1 entities)
       at VirtualStorageActivityLibrary.Delete`1.Execute(CodeActivityContext context) in ... 
       at System.Activities.CodeActivity.InternalExecute(ActivityInstance instance, ActivityExecutor executor, BookmarkManager bookmarkManager)
       at System.Activities.ActivityInstance.Execute(ActivityExecutor executor, BookmarkManager bookmarkManager)
       at System.Activities.Runtime.ActivityExecutor.ExecuteActivityWorkItem.ExecuteBody(ActivityExecutor executor, BookmarkManager bookmarkManager, Location resultLocation)


    from my simple Linq2Sql custom activity:

     [Category("Input")]
     [RequiredArgument]
     public InArgument<Func<TResult, bool>> Predicate { get; set; }
      
    
     protected override void Execute(CodeActivityContext context)
     {           
        var predicate = this.Predicate.Get(context);
    
        using(DataContext dataContext = new DataContext(this.ConnectionString.Get(context)))
        {
            var table = dataContext.GetTable<TResult>();
            var query = table.Where<TResult>(predicate);
            table.DeleteAllOnSubmit(query);
            dataContext.SubmitChanges();
        }
      }
    
    
    
    

    the problem is using a variable in the Predicate expression:
    - good example: Function(r)  r.Name = "myName"
    - error example: Function(r)  r.Name = myStringVariable


    Is this a bug or am I doing something wrong? Is there a some workaround for this case?

    Thanks in advance.


    Roman


    Roman Kiss, MVP Connected System Developer

All Replies

  • Wednesday, February 03, 2010 10:32 PM
     
     

    Hi All,

    There is a Microsoft sample ~\WF\Scenario\ActivityLibrary\Linq\LinqToSql\CS\LinqToSql for this beta2. It looks that, the problem what I having such as disposing an ActivityContext in the Linq predicate can be duplicated here, also.

    Is there some workaround for this bug?

    Thanks in advance.

    Roman


    Roman Kiss, MVP Connected System Developer
  • Saturday, February 06, 2010 1:43 AM
    Moderator
     
     
    Hi Roman,

    Just wanted to let you know someone is looking at the problem, and so far it seems like a bug. The best workaround I can suggest for now is modify your function InArgument to take a predicate with two inputs

    Function(x, y, bool)

    and pass the string variable to the custom activity as an additional InArgument (y), then in Execute feed the value to the predicate in the custom activity body.

    Then the predicate won't be directly referencing workflow variables, which seems to cause the problem.

    Tim
  • Saturday, February 06, 2010 1:48 AM
     
     
    Thanks Tim

    I will try it

    Roman

    Roman Kiss, MVP Connected System Developer
  • Monday, February 08, 2010 9:02 PM
     
     

    Hi Tim,

    I can’t use your suggested workaround in the LINQ. There are only two overloaded methods for Where such as  

     Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

     Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate);

    and addition that the predicate delegate can be more complex expression, for example:

    Function(r)  r.Name = myName and r.Id = myId and r.ValidDT  <  myDateTime and …

     

    Thanks

    Roman


    Roman Kiss, MVP Connected System Developer
  • Monday, February 08, 2010 11:44 PM
     
      Has Code
    Hi Roman,

    The problem you encountered is indeed the result of a bug, which unfortunately will not be fixed in .Net Framework 4.0.  To work around it, substitute the Visual Basic expression you used to produce the predicate with a custom activity that generates an equivalent predicate.  For example:

     

    using System;
    using System.Activities;
    
    namespace MyActivities
    {
        public class CreatePredicate<T> : CodeActivity<Func<T, bool>>
        {
            public InArgument<T> PredicateParameter { get; set; }
    
            protected override Func<T, bool> Execute(CodeActivityContext context)
            {
                T localParam = this.PredicateParameter.Get(context);
                return (p) => p.Equals(localParam);
            }
        }
    }
    

    You can then bind the PredicateParameter of this activity to a workflow variable.  Notice that the custom CreatePredicate activity evaluates the parameter bound to your workflow variable and saves its value.  This value will subsequently be used any time your predicate is evaluated, even if the value of the workflow variable was later changed.  Typically, this is the behavior you want.

    If this solution doesn't work in your particular case, please provide more details on the context in which you need to use your predicate.

    Andrew


    Senior Program Manager, Windows Worfklow Foundation and Windows Communication Foundation
  • Tuesday, February 09, 2010 8:28 AM
     
     

    Hi Andrew,

    Thanks for your suggestion. There is my code snippet from my generic Linq2Sql custom activity for Delete in the initiate post. I am using this custom activity by designer, and I would like to see how your CreatePredicate wrapper will help in the following example of the LINQ predicate:

    Function(r)  r.Name = name and r.CreatedDT < dt and r.TicketId  <> id  

    where the name is a string, dt is a DateTime and id is a guid.

     

    Thanks

    Roman


    Roman Kiss, MVP Connected System Developer
  • Wednesday, February 17, 2010 6:11 PM
     
     
    Hi Roman,

    Since I can't see your full workflow, I'm guessing a little.  I assume you create your workflow in the designer and you have your Linq2Sql custom activity dropped on the designer surface.  You then bind the Predicate argument of Linq2Sql to the VB expression you showed above.  You would need to replace this expression with the custom CreatePredicate activity I suggested.  Designer doesn't directly support binding arguments to other activities (even though the runtime does).  To make this work, you will need to create a variable of type Func<TResult, bool> (where TResult is a specific type you need) in your workflow.  Then you will need add CreatePredicate activity and bind its Result argument to that variable.  Finally, you need to bind your Linq2Sql Predicate argument to that same variable.

    I hope this helps.

    Andrew
    Senior Program Manager, Windows Worfklow Foundation and Windows Communication Foundation
  • Monday, February 22, 2010 10:21 PM
     
      Has Code
    Hi Amadeo,

    Please, could you provide implementation details how can be solved this predicate problem for my custom activity:

    public sealed class Delete<TResult> : CodeActivity where TResult : class
    {
      [Category("Input")]
      [RequiredArgument]
      public InArgument<string> ConnectionString { get; set; }
            
      [Category("Input")]
      [RequiredArgument]
      public InArgument<Func<TResult, bool>> Predicate { get; set; }
      
      protected override void Execute(CodeActivityContext context)
      {           
         var predicate = this.Predicate.Get(context);
        
         using(DataContext dataContext = new DataContext(this.ConnectionString.Get(context)))
         {
             var table = dataContext.GetTable<TResult>();
             var query = table.Where(predicate);
             table.DeleteAllOnSubmit(query);
             dataContext.SubmitChanges();
         }
       }
     }

    Note, this is a generic Linq2Sql custom activity, where TResult object is mapped to the Sql Table and the predicate can be a complex expression based on the application needs. There is no way to build for each predicate expression a custom predicate activity (wrapper). The predicate must be done declaratively way for any expression complexity, for instance:

    Function(r)  r.Name = name and r.CreatedDT < dt and r.TicketId  <> id  or ... 




    Thanks in advance.


    Roman


    Roman Kiss, MVP Connected System Developer
  • Friday, July 30, 2010 1:41 PM
     
     Proposed Answer Has Code

    Hi Roman,

    Did you get a good solution to this?

    I managed to create a workaround that allowed me to pass the predicate in as per your requirements, but it's a bit of a hack, and it only works currently if instead of having multiple variables that you pass in (in your case 'id' and 'name') you have them in a class and you have a variable of that class defined on your workflow.  So your predicate would look something like:

    Function(r)  r.Name = myType.name and r.CreatedDT < dt and r.TicketId  <> myType.id  or .

    where myType is the variable defined on the WF that contains all of the operands that you want to use in your expression (other than those actually on TResult).

    Then, change your activity to have another generic parameter where T is the same type as myType, and define an InArgument<T> on your activity.  On your workflow design surface, you need to set this arg to myType.  This is to get around the problem whereby inside the activity, the expression doesn't have access to variables defined on the WF, it will however have access to your new InArgument.

    Next, the problem is the predicate itself, because as it currently stands, the expression will still refer to the WF variable.  The workaround is to have your activity re-write the expression tree such that it refers to your InArgument rather than the WF variable.

    Firstly, change your predicate InArgument to be

    public InArgument<Expression<Func<TResult, bool>>> Predicate { get; set; }

    so that we have access to the expression tree.  Note that this does not change the way that this argument is set in the designer.  Now that we have access to the expression tree, we need to create a derivative of System.Linq.Expressions.ExpressionVisitor in order to replace the parts we need.  Currently, any references to WF variables will be in the expression tree as calls to a StrongBox<T>.GetValue method that's causing all of the problems.

    Here's my visitor class that will do the replacement:

    public class StrongBoxGetValueReplacer :ExpressionVisitor
    {
          private Expression _replacementExpression;
    
          public Expression Modify(Expression source, Expression replacement)
          {
            _replacementExpression = replacement;
            return Visit(source);
          }
          
          protected override Expression VisitMethodCall(MethodCallExpression node)
          {
            if(node.Method.Name =="GetValue")
            {
              if(node.Type == typeof(T))
              {
                return _replacementExpression;
              }
            }
            
            return base.VisitMethodCall(node);
          }
         
        }
    

    Let's say your new argument is called QueryDataContainer; in the execute method of your activity, you can use the above class to replace all variable references with the value of QueryDataContainer:

    var replacer = new StrongBoxGetValueReplacer();
    
    predicate = (Expression<Func<TResult, bool>>)
      replacer.Modify(predicate, Expression.Constant(QueryDataContainer.Get(context), typeof(T)));
    
    
    

    then, you can pass the predicate to your where clause as normal.

    So to summarise, the hack is to pass the required variable into your activity as InArgument, then rewrite the expression tree to read from data contained within your InArgument rather than the variable.

    Hopefully that solves your problem.  You could extend the activity to take in multiple variables as parameters by adding more generic parameters to the activity definition.

    If by now you've already found a better solution, please can you write back to me?

    Thanks.

     

     


    • Proposed As Answer by TobyCouchman Friday, July 30, 2010 4:04 PM
    •  
  • Thursday, August 05, 2010 2:17 PM
     
     

    Hi,

    What is T in line if(node.Type == typeof(T)) ?

    Doğu


    dogu
  • Sunday, January 23, 2011 1:57 AM
     
     Proposed Answer

    I've just hit this one in VS2010 RTM.

    I have a WIF IClaimsIdentity that I am using LINQ queries against to validate claims. I first do a Where to select a subset of claims. I then go into a FlowChart that checks Any(), then First() and Count() are called if Any() returns true.

    My workaround is to append a ToList() to the end of the original Where() in order to force the resultant type to an IList. This avoids this bug as it is no longer using some IEnumerable type for the subsequent LINQ queries.

    • Proposed As Answer by Rory_Primrose Sunday, January 23, 2011 2:14 AM
    •  
  • Monday, January 24, 2011 7:42 AM
    Moderator
     
     
    @Dogu: since Toby mentioned StrongBox<T>, I think it is a template type parameter of the class.
    Tim
  • Friday, April 01, 2011 10:05 AM
     
      Has Code

    I have just run into this problem. I have been trying to use the custom activities provided in the Entity Activities sample. I can't abide the idea of writing WF imperative code so I added a designer for ObjectContextScope activity.

    Can't quite get my head around it but it seems there is a bug in the designer when handling lambda predicates as arguments(???) Evidently VS2010 SP1 does not fix it. I think what Andrew is saying is that the workaround is to use codeactivity classes to create your predicates in code and assign the results to designer variables. So each predicate requires its own codeactivity implementation.

    Thus I created this for my purposes:

     public class GetInstrumentBySymbol : CodeActivity<Func<Instrument, bool>>
    
     {
    
      [RequiredArgument]
    
      public InArgument<String> Symbol { get; set; }
    
    
    
      protected override Func<Instrument, bool> Execute(CodeActivityContext context)
    
      {
    
       string symbol = context.GetValue(this.Symbol);
    
    
    
       if (String.IsNullOrEmpty(symbol))
    
        throw new ValidationException("symbol");
    
    
    
       return (p) => p.Symbol == symbol;
    
      }
    
     }
    
    

    Where Instrument is my entity class that has a Symbol property.

    What a drag.


    Dick Page
  • Monday, April 04, 2011 7:49 AM
     
     

    Hi,

     

    I'm aware that this's inappropriate thread but I couldn't contact you in any other way.

    I had problem regarding the execution time of the workflows and came across Ron's blog. (http://blogs.msdn.com/b/rjacobs/archive/2011/02/12/wf4-performance-tip-cache-activities.aspx?wa=wsignin1.0)

     

    You've responded to the blog pointing to an url http://blogs.msdn.com/b/rjacobs/archive/2011/02/12/wf4-performance-tip-cache-activities.aspx?wa=wsignin1.0

    Unfortunately, this link doesn't work anymore and I was hoping you could help me with the same? 

    I came to this thread as a last resort. Sorry for any inconvenience caused.


    Peace, Kavya
  • Thursday, April 07, 2011 7:29 PM
     
      Has Code

    Thanks Andrew and dickP. Here is my consolidated CreatePredicate CustomActivity after your answers. This follows the idea that Andrew mentioned above and eliminates the need to create multiple Custom Activities for each predicate you would come across.

    The code comments tell more about what I try to do:

    using System;
    using System.Activities;
    
    namespace MyWorkflow.Activities
    {
      /// <summary>
      /// Lets say you want to create a predicate to find employees by their FirstName property.
      /// Your TObj will be Employee class type (your entity class type).
      /// Your TPredicate will be string (type of the data you are going to compare) because FirstName is a string.
      /// When you drag this custom activity on to your designer
      ///   Pass the variable which holds the value of the FirstName to compare. In my example, let InFirstName be a workflow variable that holds the value XYZ.
      ///   Pass the name of the property you are going to compare. In my example, the string literal "FirstName" (so reflection can look at this).
      /// </summary>
      /// <typeparam name="TObj"></typeparam>
      /// <typeparam name="TPredicate"></typeparam>
      public class CreatePredicate<TObj,TPredicate> : CodeActivity<Func<TObj,bool>>
      {
    
        [RequiredArgument]
        public InArgument<TPredicate> PredicateParameter { get; set; } //variable that holds the value "XYZ"
    
        [RequiredArgument]
        public InArgument<string> PropertyName { get; set; }  //name of the property I am going to use for comparison.
    
        protected override Func<TObj, bool> Execute(CodeActivityContext context)
        {
          TPredicate localParam = this.PredicateParameter.Get(context);
          string propName = this.PropertyName.Get(context);
    
          //You can also add more parameters and make this even more generic to check for fields and also to use different comparison operators.
          return (p) => p.GetType().GetProperty(propName).GetValue(p,null).Equals(localParam);
        }
    
      }
    }
    

     


    Om
  • Wednesday, June 27, 2012 10:11 PM
     
     

    Hi Tim,

    Has this bug been fixed in .Net Framework 4.5?


    Art Schmidt


    • Edited by atsjr Thursday, June 28, 2012 2:14 PM
    •  
  • Thursday, July 05, 2012 11:36 PM
    Moderator
     
     
    Not as far as I know.
    Tim