Заставить выражение.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# не поддерживает какие-либо явные правила захвата для анонимных делегатов / выражений.
Ваш обходной путь - способ сделать это.