beaucrawford.net

Give me data or give me death

About the author

Author Name is someone.
E-mail me Send mail

Recent comments

Don't show

Authors

Tags

Don't show

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

    © Copyright 2010

    Hacking into the Windows Workflow Rules Engine

    On my current gig we have been toying with the idea of creating a rules engine to a control a simple work flow that centers on Microsoft Message Queue.  The main idea is to build a very simple UI that allows a Business user to enter an expression based off of a Type in our system, such as ICustomer.   We would give the user some sort of “Poor Man’s Intellisense” to help them with the syntax but, other than that, they would be on their own – just keep pressing keys until you have a valid expression (we would wrap the expression evaluation call in a try/catch).

    My first thought was to use the LINQ Dynamic Query API to allow a user to enter a statement as a string and then simply call some code that looks something like this:

    var lambda = DynamicExpression.ParseLambda(typeof(ICustomer), typeof(bool), txtExpression.Text.Trim());
    var result = (bool)lambda.Compile().DynamicInvoke(instance);

    The statement, in this case, would evaluate to a Boolean value (the conditional part of the Rule evalulation).  That looked very promising and definitely would have fit the bill for the vast majority of our needs. I wanted something more though – namely the ability to use extension methods as part of the expression and to also be able to execute method invocation statements.  I was not having any luck in modifying the LINQ Dynamic Query code to consistently do what I needed to for these items.  That led me to look elsewhere.

    I had previously heard about using the WF Rules Engine outside of WF itself at the Twin Cities Code Camp a few months ago.  It’s a pretty simple concept really and I remember thinking at the time that it looked very promising.  In an effort to pay this problem its due diligence I investigated WF in more detail.  Turns out it’s pretty easy to interact with the Rule object model directly:
    var thisType = typeof(IShipment);
    
    var currentInstance = new Shipment();
    
    var ruleSet = new RuleSet("Test");            
    var ruleValidation = new RuleValidation(thisType, null);
    
    Rule rule = new Rule("Rule 1");
    rule.ReevaluationBehavior = RuleReevaluationBehavior.Never;
    rule.Condition = Parser.ParseCondition("ShipmentDate > DateTime.Now.AddDays(-30)", ruleValidation);
    rule.ThenActions.AddRange(Parser.ParseStatementList("Queue.Send(\"StagingQueue\", this)", ruleValidation).ToArray());
    
    ruleSet.Rules.Add(rule);
    
    RuleExecution exec = new RuleExecution(new RuleValidation(currentInstance.GetType(), null), currentInstance);
    ruleSet.Execute(exec);

    The crux of my problem is obviously how to parse an arbitrarily complex expression into an an instance of RuleExpressionCondition.  If you look at the above code you will notice some references to a static class named “Parse”.  This, unfortunately, is a class that I had to create.  I noticed that WF uses the System.Workflow.Activities.Rules.Design.RuleSetDialog Windows Form to maintain a RuleSet.  I cracked this Type open in Reflector in an effort to see how they were taking the strings entered on the UI and converting them to expressions.  As I expected, the functionality for doing this is all internal.  Bummer.  Well, for now, I decided to use a little Full Trust reflection to create a wrapper around some Reflection calls:

    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Workflow.Activities.Rules;
    
    namespace WFRulesSample
    {
        public static class Parser
        {
            private const string TypeName = "System.Workflow.Activities.Rules.Parser, System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
    
            public static RuleExpressionCondition ParseCondition(string expression, RuleValidation ruleValidation)
            {
                return ExecuteMethod("ParseCondition", new object[] { ruleValidation }, new object[] { expression }) as RuleExpressionCondition;
            }
    
            public static List< RuleAction > ParseStatementList(string expression, RuleValidation ruleValidation)
            {
                return ExecuteMethod("ParseStatementList", new object[] { ruleValidation }, new object[] { expression }) as List< RuleAction >;
            }
    
            private static object ExecuteMethod(string name, object[] ctorParameters, object[] methodParameters)
            {
                var type = Type.GetType(TypeName);
                var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(RuleValidation) }, null);
                var instance = ctor.Invoke(ctorParameters);
                var method = instance.GetType().GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                return method.Invoke(instance, methodParameters);
            }
        }
    }

    It’s doubtful that the above code will ever see the light of day for this project.  I just found it interesting.

    Caveat – yes, the above code is probably not great in terms of performance.  There are numerous ways you could speed it up with the use of some DynamicMethod calls.  Good luck and happy hacking.


    Categories: Reflection | C#
    Posted by Beau on Saturday, March 14, 2009 1:03 AM
    Permalink | Comments (3) | Post RSSRSS comment feed

    Entity Framework Detached Instances

    When working with the Entity Framework you will often find yourself working with a detached Entity instance (working with Entities over a WCF service or perhaps using them as part of an n-tier architecture).  Before you can perform any data access operations dealing with this Entity you must first attach it to an ObjectContext.  This, as you might expect, is slightly more complicated than simply calling ObjectContext.Attach.

    Thankfully, the ObjectContext class has a method named “ApplyPropertyChanges”.  Calling it is pretty straightforward, as shown below in the AttchUpdated method.   It’s never that simple though is it?  It took me awhile to realize that this method only works with Scalar property types.  As stated on MSDN:

    ApplyPropertyChanges does not affect navigation properties or related objects.

    This ended up being a major show stopper for me until I figured it out.  It turns out that, for properties that are EntityReference instances, i.e. Navigation properties, you must query the Entity’s RelationshipManager (see the ApplyReferencePropertyChanges method).

     

    public static void LoadMetadataFromAssembly(this ObjectContext context)
    {
        context.MetadataWorkspace.LoadFromAssembly(context.GetType().Assembly);
    }
    
    public static void AttachUpdated(this ObjectContext context, EntityObject detachedEntity)
    {
        if (detachedEntity.EntityState == EntityState.Detached)
        {
            object currentEntity = null;
    
            if (context.TryGetObjectByKey(detachedEntity.EntityKey, out currentEntity))
            {
                context.ApplyPropertyChanges(detachedEntity.EntityKey.EntitySetName, detachedEntity);
    
                var newEntity = detachedEntity as IEntityWithRelationships;
                var oldEntity = currentEntity as IEntityWithRelationships;
    
                if (newEntity != null && oldEntity != null)
                {
                    context.ApplyReferencePropertyChanges(newEntity, oldEntity);
                }
            }
            else
            {
                throw new ObjectNotFoundException();
            }
        }
    }
    
    private static void ApplyReferencePropertyChanges(this ObjectContext context, IEntityWithRelationships newEntity, IEntityWithRelationships oldEntity)
    {
        foreach (var relatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
        {
            var oldReference = relatedEnd as EntityReference;
    
            if (oldReference != null)
            {
                var newReference = newEntity.RelationshipManager.GetRelatedEnd(oldReference.RelationshipName, 
                    oldReference.TargetRoleName) as EntityReference;
    
                if (newReference != null)
                {
                    oldReference.EntityKey = newReference.EntityKey;
                }
            }
        }
    }

    Example:

    Product product = SomeProduct();
    
    using (var context = new BuyMoreStuffEntities())
    {
        context.LoadMetadataFromAssembly();
        context.AttachUpdated(product);
        context.SaveChanges();
    }

    Categories: Entity Framework
    Posted by Beau on Monday, March 09, 2009 8:35 PM
    Permalink | Comments (0) | Post RSSRSS comment feed