Verknüpfen von Expressions zu einer neuen Expression

1. Juni 2010

Man kann LINQ Ausdrücke relativ einfach miteinander verknüpfen, indem man einfach einen Ausdruck anhängt. So lassen sich zum Beispiel Filterkriterien dynamisch anfügen, abhängig davon, ob eine Eingabe erfolgt ist, oder nicht:

   1: void List<Customer> GetCustomers(FilterData fd)
   2: {
   3:         Func<Customer, bool> filter= c=>true;
   4:         if (fd.City!=null)
   5:                 filter= c => filter(c) && (c.City==fd.City);
   6:         if (fd.Zip!=null)
   7:                 filter= c => filter(c) && (c.Zip==fd.Zip);
   8:         ...
   9: } 

Versucht man das allerdings mit LINQ to SQL oder gegen das Entity Framework hat man es nicht mehr mit einfachen Lambdas, sondern mit Expressions zu tun. Und Expressions bringen den Nachteile mit, daß sie sich nicht mehr so einfach verketten lassen… Wenn man filter im obigen Beispiel als Expression<Func<Customer, bool>> deklariert klappt das nicht mehr, weil die Aufrufsyntax bei Expressions nicht gegeben ist. Man kann sich hier retten indem man eine Liste von Expressions aufbaut, aber dann ist man auf die immer gleiche Konjunktion eingeschränkt. Will man das auflösen… viel Arbeit.

Man kann auch versuchen die einzelnen Expression zusammenzustöppseln. Problem dabei ist immer, daß die einzelnen Ausdrücke den selben Parameter verwenden müssen (nicht den gleichen), d.h. unter der Haube muß die selbe ParameterExpression-Instanz verwendet werden), was im Besipiel oben gerade nicht der Fall ist – und in C# auch nicht ausgedrückt werden kann. Die möglichen Lösungs-Varianten dazu sind:

  • Expressions komplett von Hand aufbauen. Man schreibt dan quasi seinen eigenen Compiler 😉
  • Expressions verknüpfen: über eine Expression für And bzw Or werden die beiden anderen verknüpft. Dem Parameter-Problem geht man aus dem Weg, indem man die beiden Ausdrücke per InvokeExpression auswerten läßt. Pferdefuss: das geht prinzipiell, wird aber leider nicht von LINQ2Entites/EF unterstützt, da diese ein Problem mit InvokeExpression haben.
  • Man parst die beteiligten Expressions (man muß hier wirklich den Expression-Tree ablaufen) und tauscht die Parameter-Referenzen der einen Expression durch den Parameter der anderen aus. Viel Handarbeit und übler Code.

PS: Nachdem dieser Beitrag fertig war bin ich über folgenden Blog-Beitrag gestolpert, der genau das beschreibt. Hier findet sich auch Beispiel-Code: http://blogs.msdn.com/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx