2012-06-22 3 views
10

Я в процессе написания слоя данных для части нашей системы, которая регистрирует информацию о автоматизированных заданиях, которые запускаются каждый день - имя задания , как долго это продолжалось, какой результат был и т. д.Использование LINQ ExpressionVisitor для замены примитивных параметров ссылками на свойства в выражении лямбда

Я обращаюсь к базе данных с помощью Entity Framework, но я пытаюсь скрыть эти детали от модулей более высокого уровня, и я не хочу, чтобы объекты объекта сами подвергаются воздействию.

Однако я хотел бы сделать мой интерфейс очень гибким в критериях, которые он использует для поиска информации о работе. Например, пользовательский интерфейс должен позволить пользователю выполнять сложные запросы, такие как «дать мне все задания с именем« hello », которые выполнялись с 10:00 до 11:00, что не удалось». Очевидно, это выглядит как работа для динамически построенных деревьев Expression.

Так что я хочу, чтобы мой уровень данных (хранилище), чтобы быть в состоянии сделать, это принять LINQ выражения типа Expression<Func<string, DateTime, ResultCode, long, bool>> (лямбда-выражения), а затем за кадром преобразования, что лямбда на выражение, что мой Entity Framework ObjectContext может использовать как фильтр внутри предложения Where().

В двух словах, я пытаюсь преобразовать лямбда-выражения типа Expression<Func<string, DateTime, ResultCode, long, bool>> к Expression<Func<svc_JobAudit, bool>>, где svc_JobAudit является объектом данных Entity Framework, который соответствует таблице, в которой хранится информация задания. (Четыре параметра в первом делетете соответствуют названию задания, при его запуске, результату и длительности в MS соответственно)

Я добился очень хорошего прогресса, используя класс ExpressionVisitor, пока не ударил кирпичная стена и получила InvalidOperationException с этим сообщением об ошибке:

при вызове из «VisitLambda», перезапись узла типа «System.Linq.Expressions.ParameterExpression» должен вернуть ненулевой значения из такой же тип. Альтернативно, переопределить 'VisitLambda' и изменить его, чтобы не посещать дочерние элементы этого типа.

Я полностью сбит с толку. Почему heck не позволяет мне преобразовать узлы выражения, которые ссылаются на узлы, которые ссылаются на свойства? Есть ли еще один способ сделать это?

Вот некоторые примеры кода:

namespace ExpressionTest 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Expression<Func<string, DateTime, ResultCode, long, bool>> expression = (myString, myDateTime, myResultCode, myTimeSpan) => myResultCode == ResultCode.Failed && myString == "hello"; 
      var result = ConvertExpression(expression); 
     } 

     private static Expression<Func<svc_JobAudit, bool>> ConvertExpression(Expression<Func<string, DateTime, ResultCode, long, bool>> expression) 
     { 
      var newExpression = Expression.Lambda<Func<svc_JobAudit, bool>>(new ReplaceVisitor().Modify(expression), Expression.Parameter(typeof(svc_JobAudit))); 
      return newExpression; 
     } 
    } 

    class ReplaceVisitor : ExpressionVisitor 
    { 
     public Expression Modify(Expression expression) 
     { 
      return Visit(expression); 
     } 

     protected override Expression VisitParameter(ParameterExpression node) 
     { 
      if (node.Type == typeof(string)) 
      { 
       return Expression.Property(Expression.Parameter(typeof(svc_JobAudit)), "JobName"); 
      } 
      return node; 
     } 
    } 
} 
+1

[параметр в лямбда-выражения Заменить] (http://stackoverflow.com/questions/11159697/replace-parameter-in-lambda-expression) –

ответ

6

Проблема была в два раза:

  • я недопонимание, как посетить тип лямбда-выражения. Я все еще возвращал лямбду, которая соответствовала старому делегату, вместо того, чтобы возвращать новую лямбду, чтобы соответствовать новому делегату.

  • Мне нужно было предоставить ссылку на новый экземпляр , которого я не делал.

Новый код выглядит следующим образом (обратите внимание, как посетитель теперь принимает ссылку на ParameterExpression сопоставления объекта данных Entity Framework):

class Program 
{ 
    const string conString = @"myDB"; 

    static void Main(string[] args) 
    { 
     Expression<Func<string, DateTime, byte, long, bool>> expression = (jobName, ranAt, resultCode, elapsed) => jobName == "Email Notifications" && resultCode == (byte)ResultCode.Failed; 
     var criteria = ConvertExpression(expression); 

     using (MyDataContext dataContext = new MyDataContext(conString)) 
     { 
      List<svc_JobAudit> jobs = dataContext.svc_JobAudit.Where(criteria).ToList(); 
     } 
    } 

    private static Expression<Func<svc_JobAudit, bool>> ConvertExpression(Expression<Func<string, DateTime, byte, long, bool>> expression) 
    { 
     var jobAuditParameter = Expression.Parameter(typeof(svc_JobAudit), "jobAudit"); 
     var newExpression = Expression.Lambda<Func<svc_JobAudit, bool>>(new ReplaceVisitor().Modify(expression.Body, jobAuditParameter), jobAuditParameter); 
     return newExpression; 
    } 
} 

class ReplaceVisitor : ExpressionVisitor 
{ 
    private ParameterExpression parameter; 

    public Expression Modify(Expression expression, ParameterExpression parameter) 
    { 
     this.parameter = parameter; 
     return Visit(expression); 
    } 

    protected override Expression VisitLambda<T>(Expression<T> node) 
    { 
     return Expression.Lambda<Func<svc_JobAudit, bool>>(Visit(node.Body), Expression.Parameter(typeof(svc_JobAudit))); 
    } 

    protected override Expression VisitParameter(ParameterExpression node) 
    { 
     if (node.Type == typeof(string)) 
     { 
      return Expression.Property(parameter, "JobName"); 
     } 
     else if (node.Type == typeof(DateTime)) 
     { 
      return Expression.Property(parameter, "RanAt"); 
     } 
     else if (node.Type == typeof(byte)) 
     { 
      return Expression.Property(parameter, "Result"); 
     } 
     else if (node.Type == typeof(long)) 
     { 
      return Expression.Property(parameter, "Elapsed"); 
     } 
     throw new InvalidOperationException(); 
    } 
} 
+0

Это было полезно, спасибо. –

+0

Переопределение VisitLambda в ExpressionVisitor имеет проблему с вложенными лямбда-выражениями. Предположим, что преобразованное выражение: (jobName, ranAt, resultCode, истек) => AuditLogTable.Any (a => a.JobName == имя_имя && a.Date> = ranAt) ; Суб-лямбда "а => a.JobName ..." будет обрабатываться с помощью VisitLambda и преобразуется в: svc_JobAudit => a.JobName == JobName && a.Date> = ranAt ... который не удастся. В моем случае я просто удалил переопределение (которое вызвано только для sub-lambdas и не вызвано для основного лямбда верхнего уровня) –

1

Принятый ответ «жёстко» в какой-то конкретный типы. Вот более общий редиректор выражений, чем может заменить параметр для любого другого выражения (лямбда, константа, ...). В случае лямбда-выражения подпись выражения должна изменяться для включения параметров, необходимых для замещенного значения.

public class ExpressionParameterSubstitute : System.Linq.Expressions.ExpressionVisitor 
{ 
    private readonly ParameterExpression from; 
    private readonly Expression to; 
    public ExpressionParameterSubstitute(ParameterExpression from, Expression to) 
    { 
     this.from = from; 
     this.to = to; 
    } 

    protected override Expression VisitLambda<T>(Expression<T> node) 
    { 
     if (node.Parameters.All(p => p != this.from)) 
      return node; 

     // We need to replace the `from` parameter, but in its place we need the `to` parameter(s) 
     // e.g. F<DateTime,Bool> subst F<Source,DateTime> => F<Source,bool> 
     // e.g. F<DateTime,Bool> subst F<Source1,Source2,DateTime> => F<Source1,Source2,bool> 

     var toLambda = to as LambdaExpression; 
     var substituteParameters = toLambda?.Parameters ?? Enumerable.Empty<ParameterExpression>(); 

     ReadOnlyCollection<ParameterExpression> substitutedParameters 
      = new ReadOnlyCollection<ParameterExpression>(node.Parameters 
       .SelectMany(p => p == this.from ? substituteParameters : Enumerable.Repeat(p, 1)) 
       .ToList()); 

     var updatedBody = this.Visit(node.Body);  // which will convert parameters to 'to' 
     return Expression.Lambda(updatedBody, substitutedParameters); 
    } 

    protected override Expression VisitParameter(ParameterExpression node) 
    { 
     var toLambda = to as LambdaExpression; 
     if (node == from) return toLambda?.Body ?? to; 
     return base.VisitParameter(node); 
    } 
}