Tech:
Quando eseguo questo metodo lancia "InvalidOperationException: Sequence non contiene alcun elemento corrispondente" sulla valutazione di CurrentGrade. Perché getta e come posso risolverlo?
Ho un metodo di ricerca che filtra su un bel po 'di proprietà su un grande set di dati (10.000 utenti con migliaia di entità correlate). Sto cercando di ottimizzare la query e non voglio eseguire la query fino a quando non è stato effettuato tutto il filtraggio. Mentre si utilizza ToList (), il metodo funziona, preferirei lavorare contro un IQueryable ed eseguire la query al termine del filtraggio.
Sono abbastanza sicuro che questo ha funzionato prima di aggiornare EF Core da 1.x a 2.0.
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 () funziona bene per il filtraggio su chiavi esterne, ma quando si effettua il filtraggio su una raccolta di proprietà di navigazione usando .Where () si lancia su gradi ICollection su Member appena prima che il filtro fosse incluso.
Questo è il metodo all'interno di ApplyFiltering () che genera:
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 è una proprietà calcolata su un membro, Grade è solo un 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>();
}
Il problema è che la proprietà non mappata ("calcolata") sta causando la valutazione del client , ma al momento EF valuta la parte client della clausola Where
, le proprietà di navigazione non sono ancora state caricate, quindi la raccolta dei Grades
è vuota (come è stata inizializzato con la new Collection<MemberGrade>
- se rimuovi l'inizializzatore, otterrai NullReferenceException
).
Ora, probabilmente potrebbe essere trattato come un bug di EF Core. Ma consiglio vivamente di non utilizzare proprietà non mappate nelle query LINQ in generale, e in particolare nelle condizioni del filtro delle query. Anche se funzionano, la valutazione del client causerà il caricamento di molti dati in memoria solo per applicare il filtro lì, piuttosto che sul lato database (SQL).
Assicurati anche di usare costrutti traducibili SQL. Ad esempio, Last
/ LastOrDefault
non ha una traduzione SQL naturale, mentre FirstOrDefault
ha, quindi il solito schema è OrderByDescending().FirstOrDefault()
anziché OrderBy().LastOrDefault()
.
Detto questo, la soluzione di valutazione lato server funzionante nel tuo caso sarebbe così:
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));