Заставить выражение.NET использовать текущее значение

Я пытаюсь выяснить, есть ли способ заставить выражение C# преобразовать часть выражения в значение. Я вызываю метод, который принимает выражение, определяющее запрос. У меня есть ряд значений в объекте, который идентифицирует запрос. Вот упрощенный пример:

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

Это не удается. Исходя из ошибки, которую я вижу, похоже, что выражение пытается включить в выражение "identifier.Id" вместо разрешения "identifier.Id" в его значении, равном 5. Работает следующий код:

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

Хотя это работает, это упрощенный пример, и в моем реальном коде это было бы больно. Итак, мой вопрос: есть ли какой-то синтаксис, который вы можете использовать, чтобы обернуть часть выражения, чтобы заставить его преобразовать в значение?

2 ответа

Решение

В этой записи блога обсуждается, как можно упростить Expression, Он использует двухпроходный подход, при котором сначала помечает все узлы, которые не являются параметрами и не имеют параметров, как дочерние, а затем выполняет еще один проход, где он оценивает эти узлы, так что все, что можно вычислить без опираясь на параметр оценивается.

Вот код с несколькими незначительными изменениями:

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;
        }
    }
}

И дополнительный метод, так что он может быть вызван на IQueryable<T> а не 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));
}

Теперь вы можете написать:

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

И в итоге получается запрос, который эффективно:

context.SomeMethod(i=>i.Id == 5)

C# не поддерживает какие-либо явные правила захвата для анонимных делегатов / выражений.

Ваш обходной путь - способ сделать это.

Другие вопросы по тегам