2012-06-22 25 views
10

Jestem w trakcie pisania warstwy danych dla części naszego systemu, która rejestruje informacje o automatycznych zleceniach uruchamianych każdego dnia - nazwa zlecenia , jak długo działał, jaki był wynik, itp.Używanie LINQ ExpressionVisitor do zamiany parametrów pierwotnych na odwołania do właściwości w wyrażeniu lambda

Mówię do bazy danych za pomocą Entity Framework, ale staram się ukryć te szczegóły z modułów wyższego poziomu i nie chcę obiekty obiektów same się ujawniają.

Jednak chciałbym, aby mój interfejs był bardzo elastyczny w kryteriach, które wykorzystuje do wyszukiwania informacji o zadaniach. Na przykład interfejs użytkownika powinien umożliwiać użytkownikowi wykonywanie złożonych zapytań, takich jak "daj mi wszystkie zadania o nazwie" cześć ", które trwały między godziną 10:00 a 11:00, co nie powiodło się." Oczywiście wygląda to jak zadanie dla dynamicznie budowanych drzewek Expression.

Więc co chciałbym moje warstwy danych (repozytorium), aby być w stanie zrobić, to zaakceptować LINQ wyrażeń typu Expression<Func<string, DateTime, ResultCode, long, bool>> (wyrażenie lambda), a następnie za kulisami przekonwertować do wyrażenia lambda, że ​​moja Entity Framework ObjectContext mogą korzystać jako filtr wewnątrz klauzuli Where().

W skrócie, próbuję przekonwertować wyrażenia lambda typu Expression<Func<string, DateTime, ResultCode, long, bool>> do Expression<Func<svc_JobAudit, bool>>, gdzie svc_JobAudit obiektem danych Entity Framework, która odpowiada tabeli, gdzie informacje są przechowywane zadanie. (Cztery parametry pierwszego uczestnika odpowiadają nazwie zadania, wynikowi, i ile czasu zajęło mu odpowiednio MS)

Robiłem bardzo duże postępy, używając klasy ExpressionVisitor, dopóki nie trafiłem mur z cegły i otrzymał InvalidOperationException z tym komunikatem o błędzie:

Wywołany z „VisitLambda”, przepisywanie węzeł typu „System.Linq.Expressions.ParameterExpression” musi wrócić niezerowym wartość Instrumentu taki sam typ. Możesz też zastąpić "VisitLambda" i , aby nie odwiedzać dzieci tego typu.

Jestem kompletnie zaskoczony. Dlaczego do cholery nie pozwala mi konwertować węzłów wyrażeń, które odwołują się do parametrów do węzłów, które odwołują się do właściwości? Czy istnieje inny sposób, aby o tym poradzić?

Oto przykładowy kod:

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

[wymienić parametr ekspresji lambda] (http://stackoverflow.com/questions/11159697/replace-parameter-in-lambda-expression) –

Odpowiedz

6

Problem był dwojaki:

  • byłem nieporozumienie jak odwiedzić typ wyrażenia lambda. Nadal zwracałem lambdę, która pasowała do starego delegata, zamiast zwracać nową lambdę, aby pasowała do nowego delegata.

  • Potrzebowałem odwołania do nowej instancji ParameterExpression, której nie robiłem.

Nowy kod wygląda następująco (zauważ, jak użytkownik akceptuje teraz odwołanie do ParameterExpression pasującą do obiektu danych 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

to przydatne, dzięki. –

+0

Zastąpienie funkcji VisitLambda w ExpressionVisitor ma problem z zagnieżdżonymi wyrażeniami lambda. Załóżmy (przykładowy przykład), że konwertowane wyrażenie to: (jobName, ranAt, resultCode, minął) => AuditLogTable.Any (a => a.JobName == jobName && a.Date> = ranAt) ; sub-N "a => a.JobName ..." będzie się obróbce przez VisitLambda i przekształcono: svc_JobAudit => a.JobName == JOBNAME && a.Date> = Ranat ... co zawiedzie. W moim przypadku właśnie usunąłem nadpisanie (które jest wywoływane tylko dla podrzędnych lambd i nie jest wywoływane dla głównej lambda najwyższego poziomu) –

1

Zaakceptowanych odpowiedź jest „na sztywno” do niektórych specyficznych typy. Oto bardziej ogólny przepisujący zapis niż może zastąpić parametr dla dowolnego innego wyrażenia (lambda, stała, ...). W przypadku wyrażenia lambda, sygnatura wyrażenia musi się zmienić, aby uwzględnić parametry wymagane przez podstawioną wartość.

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