Search code examples
entity-frameworkparameterslambdaexpression-treesexpressionvisitor

How to bind parameters in replaced expression nodes in Entity Framework on the fly


I'm trying to replace a function call like (simplified) Utility.GetString(MyEntity.SomePropertyWithRelatedEntity)=="abc" with an expression visitor into something like p => p.SubRelatedEntities.FirstOrDefault(sre => sre.SomeFlag==true).SomePropertyWithRelatedEntity.

It means, the datamodel goes like: MyEntity -> RelatedEntity -> SubRelatedEntity

I'm trying to return a string value from the SubRelatedEntity, based on some rules in the RelatedEntity, so I don't have to re-write / copy/paste the whole filtering rules in every usage; that's why I put inside a "call-signature", so my expression visitor can identify it and replace the fake-call to Utility.GetString to some complicated lambda expressions.

My expression visitor contains something like:

    public override Expression Visit(Expression node)
    {
        if (node == null)
            return null;

        Expression result = null;

        if (node.NodeType == ExpressionType.Call)
        {
            MethodCallExpression mce = node as MethodCallExpression;
            if (mce.Method.DeclaringType == typeof(Utility) && mce.Method.Name == "GetString")
            {
                Expression<Func<RelatedEntity, string>> exp = re => re.SubRelatedEntities.FirstOrDefault(sre => sre.SomeFlag == true).SomeStringValue;
                result = exp.Body;

            }
            else
                result = base.Visit(node);
        }
        else
            result = base.Visit(node);

        return result;

    }

Now, the problem is, the "sre" parameter is not bound when called the injected lambda expression. After much research, I see the lambda parameters should be replaced with another expression visitor, specifically searching for the new parameters and replacing them with the old ones. In my situation, however, I don't have an "old parameter" - I have the expression MyEntity.SomePropertyWithRelatedEntity (e.g. an property filled with the related entities) which I need to insert somehow in the generated lambda.

I hope my problem is understandable. Thank you for any insights!


Solution

  • After getting no answers for long time and trying hard to find a solution, I've solved it at the end :o)! It goes like this:

    The newly injected lambda expression gets an ParameterExpression - well, this is a 'helper', used when directly calling the lambda, what I don't want (hence, 'parameter not bound' exception when ToEnumerable is called). So, the clue is to make a specialized ExpressionVisitor, which replaces this helper with the original expression, which is of course available in the Arguments[] for the method call, which I try to replace.

    Works like a charm, like this you can reuse the same LINQ expressions, something like reusable sub-queries, instead of writing all the same LINQ stuff all time. Notice as well, that expression calling a method is not allowed in EF, in Linq2Sql it worked. Also, all the proposed web articles only replace the parameter instances, when constructing/merging more LINQ expressions together - here, I needed to replace a parameter with an faked-method-call argument, e.g. the method should not be called, it only stands for a code-marker, where I need to put my LINQ sub-query.

    Hope this helps somebody, at the end it's pretty simple and logical, when one knows how the expression trees are constructed ;-).

    Bye, Andrej