2016-06-29 7 views
3

Ich erstelle eine Methode, die ein oder mehrere Kriterien für die Abfrage einer Datenbank mit LINQ verwendet. Ich habe dies:Dynamisches ODER in LINQ ohne den PredicateBuilder

public ICollection<MyClass> FindAllBy(params Expression<Func<MyClass, bool>>[] criteria) 
    { 
     using (var ctx = new MyContext()) 
     { 
      IQueryable<MyClass> result = ctx.MyClasses.AsNoTracking(); 

      if (criteria != null && criteria.Length > 0) 
      { 
       foreach (var item in criteria) 
       { 
        result = result.Where(item); 
       } 
      } 

      return result.ToList(); 
     } 
    } 

Dies hat den Effekt, dass, wenn ich für ein Objekt suche mit Id 1 und einem mit Id 2 ich nichts bekommen, da keine Zeile hat sowohl eine Id von 1 und 2. So muß ich eine OR-Klausel. Ich fand diese Seite:

http://www.albahari.com/nutshell/predicatebuilder.aspx

, die eine PredicateBuilder Klasse hat, die ich dies machen folgende Zwecke verwendet:

public ICollection<PhotoFile> FindAllBy(params Expression<Func<PhotoFile, bool>>[] criteria) 
    { 
     using (var ctx = new CotanContext()) 
     { 
      var predicate = PredicateBuilder.False<PhotoFile>(); 

      if (criteria != null && criteria.Length > 0) 
      { 
       foreach (var item in criteria) 
       { 
        predicate = predicate.Or(item); 
       } 
      } 

      return ctx.PhotoFiles.Where(predicate).ToList(); 
     } 
    } 

Mein Code unterscheidet sich geringfügig von der Seite, dass ich in einem Ausdruck gelangen in die Methode, die ich dann in die Predicate.Or-Methode übergeben.

Die obige Methode gibt einen The LINQ expression node type 'Invoke' is not supported in LINQ to Entities. Fehler. Das macht Sinn, da Entity Framework nicht weiß, wie man diesen Code in eine gültige Abfrage übersetzt.

Die Lösung auf der verlinkten Seite ist das Herunterladen ihres Nuget-Pakets oder Quellcodes, wodurch dieser Code funktioniert. Allerdings fühle ich mich nicht wohl dabei, mehrere Hundert Zeilen von unbekanntem und scheinbar unerprobtem Code für eine einzelne Funktion einzubauen, die meiner Meinung nach vor einiger Zeit von Microsoft in LINQ eingebaut sein sollte. Auch der Hauptentwickler meines Projekts hat in der Vergangenheit dringend davon abgeraten, unbekannte Pakete zu verwenden, die nicht direkt von Microsoft stammen. Ich arbeite mit sensiblen Informationen, also wäre ich lieber sicher als Nachsicht.

Also meine Frage: Gibt es eine Möglichkeit, eine OR-Funktion in LINQ zu erhalten, ohne ein externes Nuget-Paket verwenden zu müssen?

+0

Müssen Sie wirklich die Klauseln in Ihre Funktion übergeben? Warum nicht ein IQueryable <> zurückgeben und dem Anrufer überlassen, was zu tun ist? – Neil

+0

In der ersten Methode zumindest mache ich das. Ich mache das IQueryable nur zu einem IEnumerable, wenn ich ToList() aufruft. Ich verwende die Where() - Überladung, die Ausdrücke akzeptiert und ein anderes IQueryable zurückgibt. Und die Or-Methode im zweiten Beispiel gibt nur einen anderen Ausdruck zurück, sodass auch dort keine Datenbank aufgerufen wird. – ohyeah

+2

Verwenden Sie [dieses Prädikat Builder] (https://patemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/) statt –

Antwort

4

Wie ich in den Kommentaren erwähnt, können Sie die Universal PredicateBulder oder die Klasse von meiner Antwort auf Establish a link between two lists in linq to entities where clause verwenden können.

Sie können aber erheblich die Methoden wie die von Ihrem Beispiel vereinfachen, indem Sie diese einfache Erweiterung Methode:

public static class QueryableExtensions 
{ 
    public static IQueryable<T> WhereAny<T>(this IQueryable<T> source, IEnumerable<Expression<Func<T, bool>>> predicates) 
    { 
     if (predicates == null || !predicates.Any()) return source; 
     var predicate = predicates.Aggregate((a, b) => Expression.Lambda<Func<T, bool>>(
      Expression.OrElse(a.Body, b.Body.ReplaceParameter(b.Parameters[0], a.Parameters[0])), 
      a.Parameters[0])); 
     return source.Where(predicate); 
    } 
} 

, die diese Helfer wiederum verwendet:

public static class ExpressionUtils 
{ 
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target) 
    { 
     return new ParameterReplacer { Source = source, Target = target }.Visit(expression); 
    } 

    class ParameterReplacer : ExpressionVisitor 
    { 
     public ParameterExpression Source; 
     public Expression Target; 
     protected override Expression VisitParameter(ParameterExpression node) 
     { 
      return node == Source ? Target : base.VisitParameter(node); 
     } 
    } 
} 

Nun ist die Probe Methode konnte sei einfach wie das:

public ICollection<PhotoFile> FindAllBy(params Expression<Func<PhotoFile, bool>>[] criteria) 
{ 
    using (var ctx = new CotanContext()) 
     return ctx.PhotoFiles.WhereAny(criteria).ToList(); 
} 
0

Kurze Antwort: ja. Wenn Sie sich den PredicateBuilder-Code ansehen, verwendet er Ausdrücke, um Ihre Abfrage zu erstellen. Sie könnten also versuchen, dieses Verhalten nachzubilden, aber versuchen Sie, wie bereits zuvor, Ihren Code zu reparieren.

Ich benutze PredicateBuilder viel in meinen Repository-Klassen und ich erinnere mich, das gleiche Problem zu haben. Ich löste das Problem durch Zugabe von AsExpandable() (im LinqKit Namespace) in jedem Aufruf Entity Framework:

return ctx.PhotoFiles.AsExpandable().Where(predicate).ToList(); 
+0

Das war auch meine erste Idee (tolle Köpfe denken gleich :-)). Die Erweiterung AsExpandable() hat jedoch noch einige andere Abhängigkeiten, die wiederum Abhängigkeiten haben, die mich gezwungen haben, einen Großteil des Quellcodes zu kopieren, wenn ich ihn wirklich benutzen wollte. – ohyeah

+0

Um welche Abhängigkeiten kümmern Sie sich? Basierend auf dem LinqKit Nuget-Paket sind die Abhängigkeiten ziemlich häufige Assemblys. – hbulens