Force a .NET Expression to Use Current Value
Asked Answered
R

2

0

I am trying to figure out if there is a way to force a C# expression to convert part of the expression to a value. I am calling a method that accepts an expression that defines a query. I have a series of values in an object that identifies the query. Here is a simplified example:

var identifier = new { id = 5 };
context.SomeMethod(i=>i.Id == identifier.Id);

This fails. Based on the error I'm seeing it looks like the expression is attempting to incorporate "identifier.Id" into the expression instead of resolving "identifier.Id" to its value, which is 5. The following code works:

var id = 5;
context.SomeMethod(i=>i.Id == id)

Although this works, this is a simplified example and in my actual code this would be painful. So my question is, is there some syntax you can use to wrap part of an expression to force it to resolve to a value?

Rockel answered 18/5, 2015 at 16:12 Comment(5)
what query provider are you using? Most will do this (at least in most common cases) as a part of building the query.Stab
@Stab I believe his problem is around "identifier.Id" being able to change after he's defined the expression, and he wants "Id" to be captured by value instead.Sassenach
@CoryNelson Anonymous objects are immutable; it can't change.Stab
"simplified example" leaves it open to question if he's actually using an anonymous object or not. I assume not based on the rest.Sassenach
Thank you! This has all of the information I needed to get this resolve. I ended up using the 'Evaluator' class directly instead of the 'Simplify' method.Rockel
S
2

In this blog entry it discusses how one can simplify an Expression. It does a two-pass approach in which it first marks all of the nodes that aren't parameters and don't have and parameters as children, and then it does another pass where it evaluates those nodes so that anything that can be computed without relying on the parameter is evaluated.

Here is the code with a few minor tweaks:

public static class Evaluator
{
    /// <summary>
    /// Performs evaluation & replacement of independent sub-trees
    /// </summary>
    /// <param name="expression">The root of the expression tree.</param>
    /// <param name="fnCanBeEvaluated">A function that decides whether a given expression node can be part of the local function.</param>
    /// <returns>A new tree with sub-trees evaluated and replaced.</returns>
    public static Expression PartialEval(Expression expression, Func<Expression, bool> fnCanBeEvaluated)
    {
        return new SubtreeEvaluator(new Nominator(fnCanBeEvaluated).Nominate(expression)).Eval(expression);
    }

    /// <summary>
    /// Performs evaluation & replacement of independent sub-trees
    /// </summary>
    /// <param name="expression">The root of the expression tree.</param>
    /// <returns>A new tree with sub-trees evaluated and replaced.</returns>
    public static Expression PartialEval(Expression expression)
    {
        return PartialEval(expression, Evaluator.CanBeEvaluatedLocally);
    }

    private static bool CanBeEvaluatedLocally(Expression expression)
    {
        return expression.NodeType != ExpressionType.Parameter;
    }

    /// <summary>
    /// Evaluates & replaces sub-trees when first candidate is reached (top-down)
    /// </summary>
    class SubtreeEvaluator : ExpressionVisitor
    {
        HashSet<Expression> candidates;

        internal SubtreeEvaluator(HashSet<Expression> candidates)
        {
            this.candidates = candidates;
        }

        internal Expression Eval(Expression exp)
        {
            return this.Visit(exp);
        }

        public override Expression Visit(Expression exp)
        {
            if (exp == null)
            {
                return null;
            }
            if (this.candidates.Contains(exp))
            {
                return this.Evaluate(exp);
            }
            return base.Visit(exp);
        }

        private Expression Evaluate(Expression e)
        {
            if (e.NodeType == ExpressionType.Constant)
            {
                return e;
            }
            LambdaExpression lambda = Expression.Lambda(e);
            Delegate fn = lambda.Compile();
            return Expression.Constant(fn.DynamicInvoke(null), e.Type);
        }
    }

    /// <summary>
    /// Performs bottom-up analysis to determine which nodes can possibly
    /// be part of an evaluated sub-tree.
    /// </summary>
    class Nominator : ExpressionVisitor
    {
        Func<Expression, bool> fnCanBeEvaluated;
        HashSet<Expression> candidates;
        bool cannotBeEvaluated;

        internal Nominator(Func<Expression, bool> fnCanBeEvaluated)
        {
            this.fnCanBeEvaluated = fnCanBeEvaluated;
        }

        internal HashSet<Expression> Nominate(Expression expression)
        {
            this.candidates = new HashSet<Expression>();
            this.Visit(expression);
            return this.candidates;
        }

        public override Expression Visit(Expression expression)
        {
            if (expression != null)
            {
                bool saveCannotBeEvaluated = this.cannotBeEvaluated;
                this.cannotBeEvaluated = false;
                base.Visit(expression);
                if (!this.cannotBeEvaluated)
                {
                    if (this.fnCanBeEvaluated(expression))
                    {
                        this.candidates.Add(expression);
                    }
                    else
                    {
                        this.cannotBeEvaluated = true;
                    }
                }
                this.cannotBeEvaluated |= saveCannotBeEvaluated;
            }
            return expression;
        }
    }
}

And an additional method so that it can be called on an IQueryable<T> rather than an Expression

class Query<T> : IQueryable<T>
{
    private IQueryProvider provider;
    private Expression expression;
    public Query(IQueryProvider provider, Expression expression)
    {
        this.provider = provider;
        this.expression = expression;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return ((IEnumerable<T>)this.Provider.Execute(this.Expression)).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)this.Provider.Execute(this.Expression)).GetEnumerator();
    }

    public Type ElementType
    {
        get { return typeof(T); }
    }

    public Expression Expression
    {
        get { return expression; }
    }

    public IQueryProvider Provider
    {
        get { return provider; }
    }
}

public static IQueryable<T> Simplify<T>(this IQueryable<T> query)
{
    return new Query<T>(query.Provider, Evaluator.PartialEval(query.Expression));
}

You can now write:

var identifier = new { id = 5 };
var query context.SomeMethod(i=>i.Id == identifier.Id)
    .Simplify();

And end up with a query that is effectively:

context.SomeMethod(i=>i.Id == 5)
Stab answered 18/5, 2015 at 16:33 Comment(0)
S
0

C# does not support any sort of explicit capture rules for anonymous delegates / expressions.

Your workaround is the way to do it.

Sassenach answered 18/5, 2015 at 16:16 Comment(5)
These aren't delegates, they're expressions, so you can actually do this, because you have access to all of the information in the expression to actually evaluate the value. Not that it's particularly easy.Stab
The underlying expressions implementation does support constant values of course, but not in the way he's letting C# construct them.Sassenach
The point is one can transform an expression that he has into the expression that he wants.Stab
It is possible using the ExpressionVisitor but a) its beyond the scope of this question b) there is no obvious junction point for an ExpressionVisitor in Entity Framework/L2S.Scaly
@Scaly If you think that it's too much work (something I could respect) then voting to close the question as Too Broad would be fine. Saying that it couldn't be done in an answer though is not correct. That the solution is hard doesn't mean it doesn't exist.Stab

© 2022 - 2024 — McMap. All rights reserved.