2017-08-25 7 views
3

Tech:EF Core 2.0 - Filterung IQueryable Navigationseigenschaft Sammlung leer

  • EF-Core 2.0.0
  • Asp.Net Kern Mvc 2.0.0

Wenn ich diese Methode ausführen es wirft "InvalidOperationException: Sequence enthält kein passendes Element" bei der Bewertung von CurrentGrade. Warum wirft es und wie kann ich es beheben?

Ich habe eine Suchmethode, die auf ziemlich viele Eigenschaften auf einem großen Dataset (10 000 Benutzer mit Tausenden von verbundenen Entitäten) filtert. Ich versuche, die Abfrage zu optimieren, und ich möchte die Abfrage nicht ausführen, bis alle Filterung vorgenommen worden ist. Während die Verwendung von ToList() die Methode funktioniert, würde ich lieber gegen ein IQueryable arbeiten und die Abfrage ausführen, wenn das Filtern durchgeführt wird.

Ich bin mir ziemlich sicher, dass dies funktionierte, bevor EF Core von 1.x auf 2.0 aktualisiert wurde.

public MemberQueryResult Search(MemberQuery filter) 
     { 
      var query = Context.Users 
       .Include(x => x.Honours) 
       .Include(x => x.Grades) 
       .Include(x => x.Strokes) 
       .Include(x => x.Posts) 
       .Include(x => x.Loge) 
       .AsNoTracking(); 

      query = query.ApplyFiltering(filter); 

      return result; 
     } 

ApplyFiltering() funktioniert gut für die Filterung auf Fremdschlüssel aber beim Filtern auf einer Navigationseigenschaft Sammlung mit .Where() wirft es auf ICollection Sorten auf Mitglied, wenn kurz vor der Filterung es aufgenommen wurde.

Dies ist die Methode in ApplyFiltering(), die wirft:

private static IQueryable<Member> SearchByCurrentGradeRange(MemberQuery filter, IQueryable<Member> result) 
    { 
     if (filter.GradeRange == null) return result; 

     var gradeRange = filter.GradeRange.Split(','); 
     var gradeFrom = (Grade)int.Parse(gradeRange[0]); 
     var gradeTo = (Grade)int.Parse(gradeRange[1]); 

     result = result.Where(x => x.CurrentGrade >= gradeFrom && x.CurrentGrade <= gradeTo); 

     return result; 
    } 

CurrentGrade ist eine berechnete Eigenschaft auf ein Mitglied ist Grade nur eine ENUM .:

public sealed class Member : IdentityUser 
{ 
     public Grade CurrentGrade => Grades.OrderBy(x => x.Grade).Last(x => x.ReceivedDate != null).Grade; 

     public ICollection<MemberGrade> Grades { get; set; } = new Collection<MemberGrade>(); 

} 
+0

versuchen, ein (x => x.CurrentGrade), um Ihr Suchobjekt einschließen hinzufügen. –

+0

@LazyCoder Ich bekomme eine Ausnahme: 'Eine nicht behandelte Ausnahme trat während der Verarbeitung der Anfrage auf. InvalidOperationException: Die Eigenschaft 'CurrentGrade' ist keine Navigationseigenschaft des Entitätstyps 'Member'. Die Methode 'Include (string)' kann nur mit einem '.' getrennte Liste der Navigationseigenschaftsnamen.' – Lindeberg

+0

Ja, ich sehe es jetzt, also das ist es nicht. Aber ich denke, weil Sie tun .AsNoTracking() und dann versuchen, .Wohin() es später im Leben, ist es nicht mögen, dass ... vielleicht versuchen, das fallen zu lassen, nicht .ToList() lassen Sie es so wie es ist. –

Antwort

4

Das Problem ist, dass das nicht zugeordnete ("berechnete") Eigenschaft verursacht client evaluation, aber zu dem Zeitpunkt, an dem EF den Client-Teil der Where-Klausel auswertet, sind die Navigationseigenschaften noch nicht geladen, daher ist Ihre Grades Sammlung leer (wie es i initialisiert mit new Collection<MemberGrade> - wenn Sie den Initialisierer entfernen, dann erhalten Sie NullReferenceException).

Jetzt könnte es wahrscheinlich als EF Core Bug behandelt werden. Aber ich empfehle dringend nicht verwendet nicht zugeordnete Eigenschaften in LINQ Abfragen im Allgemeinen und insbesondere in Abfrage Filterbedingungen. Selbst wenn sie funktionieren, führt die Client-Auswertung dazu, dass viele Daten im Speicher geladen werden, nur um den Filter dort anzuwenden, und nicht auf der Seite der Datenbank (SQL).

Stellen Sie außerdem sicher, dass Sie übersetzbare SQL-Konstrukte verwenden. Zum Beispiel haben Last/LastOrDefault keine natürliche SQL-Übersetzung, während FirstOrDefault tut, so ist das übliche Muster OrderByDescending().FirstOrDefault() anstatt OrderBy().LastOrDefault().

Damit wird gesagt, die Arbeitsserverseitige Auswertung Lösung in Ihrem Fall so sein würde:

result = result.Where(m => m.Grades 
    .Where(x => x.ReceivedDate != null).OrderByDescending(x => x.Grade).Take(1) 
    .Any(x => x.Grade >= gradeFrom && x.Grade <= gradeTo)); 
+0

Danke für die tolle, richtige Antwort. Könnten Sie uns auch einige Ideen geben, wie Sie das Objekt objektorientiert halten können, um berechnete Eigenschaften wie diese in LINQ to SQL zu verwenden? @Ivan Stoev Ich denke, es muss eine gemeinsame Best Practice dafür geben, aber ich habe Probleme bei der Suche auf Google .. Es gibt einige Gespräche über Ausdrücke, aber das fühlt sich immer noch nicht optimal an, weil die Logik auf dem Objekt mit der Eigenschaft sein sollte oder Methode, oder? – Lindeberg

+1

Richtig. Aber die OOP, insbesondere die Einkapselung, spielt nicht gut mit der LINQ-Abfrageübersetzung, die auf dem entgegengesetzten Prinzip basiert - Wissen, d. H. In der Lage zu sein, den Ausdrucksbaum zu durchlaufen und die erkannten Methoden/Konstrukte abzubilden. Zu dieser Zeit gibt es überhaupt keine Objekte. Kurz, in EFC müssen Sie wählen zwischen (1) objektorientiert, aber ineffizient und (2) nicht objektorientiert, aber effizient. –

+0

Danke. Heilige Scheiße, warum wird nicht darüber geredet, frage ich mich, während ich mit einer großen Datenbank arbeite ... In EFC sagst du - meinst du es ist anders in EF6? Zumindest gibt es Unterstützung für das Paket, das ich "DelegateDecompiler" gefunden habe, das es irgendwie löst. – Lindeberg

Verwandte Themen