Sto rifattorando un po 'un progetto e sono tornato a un problema che non ho mai risolto in passato. Sto cercando di eseguire più filtri su una query di un db EF Core.
In passato avevo tentato di impostare una serie di istruzioni Where che controllavano se l'istruzione filter era null O abbinando il filtro.
Ciò ha restituito una nullReferenceException da qualche parte nella query. Ho risolto il problema eseguendo la mia query senza i filtri e applicando successivamente i filtri al mio Elenco.
Sono tornato e ho creato un'estensione WhereIf e speravo che potesse risolvere i miei problemi, rendendo il codice un po 'più pulito, ma lo stesso problema si presenta.
Al momento ho quattro filtri che sto cercando di eseguire sulla query e passa correttamente il filtro iniziale, ma se viene scelto uno qualsiasi degli altri tre filtri, la query ha nullReferenceException.
Questo funziona di nuovo se ottengo un elenco da una query generale e dal primo filtro, quindi successivamente applico i filtri al mio elenco.
Questo è quello che mi piacerebbe fare:
IQueryable<Film> films = _context.Films
.Include(f => f.Media)
.Include(f=> f.Audio)
.Include(f => f.FilmGenres)
.ThenInclude(fg => fg.Genre)
.WhereIf(!string.IsNullOrEmpty(vm.SearchValue), f => f.Name.ToLower().Contains(vm.SearchValue.ToLower()))
.WhereIf(!string.IsNullOrEmpty(vm.MediaFilter), f => f.Media.Name == vm.MediaFilter)
.WhereIf(!string.IsNullOrEmpty(vm.AudioFilter), f => f.Audio.Name == vm.AudioFilter)
.WhereIf(!string.IsNullOrEmpty(vm.GenreFilter), f => f.FilmGenres.Any(fg => fg.Genre != null && fg.Genre.Name == vm.GenreFilter));
Ecco il metodo WhereIf:
public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool condition, Expression<Func<TSource, bool>> predicate)
{
// Performs a Where only when the condition is met
if (condition)
{
source = source.Where(predicate);
return source;
}
return source;
}
Il filtro su vm.SearchValue va bene e quando lo passo, il valore è IQueryable come previsto. Una volta che colpisce uno degli altri filtri, ritorna con nullReferenceException (quando alla fine arriva alla ToList () in seguito). Se guardo il valore della fonte prima di tornare, mostra che ha l'eccezione nulla nella vista dei risultati.
Ho provato a fare ogni riga una per una (con un film = film.Where (...)). Ho provato a saltare WhereIf e a fare semplicemente if e uno standard Where, e tutto questo ha lo stesso risultato.
È solo quando creo un oggetto Elenco, popolato da una query generale dei dati, e quindi filtra l'oggetto Elenco che riesco a farlo funzionare.
Quindi, qual è il problema con il filtro su un IQueryable in EF Core? Non è permesso o sto facendo qualcosa di sbagliato?
Aggiornamento: tutti gli oggetti Film hanno oggetti Media / Audio / FilmGenre e tutto è stato incluso. E ho verificato che gli elementi nell'origine IQueryable hanno tutti questi elementi prima dell'istruzione Where nel metodo WhereIf.
Ho provato a separare ciascuna istruzione del filtro separatamente, e ciò include saltare il metodo WhereIf e usare anche le istruzioni if.
Inoltre, è possibile selezionare un solo filtro alla volta (per ora). Quelli che non sono selezionati comportano che la condizione sia falsa e non ci sono problemi. Solo singhiozzo quando si lavora su un filtro attivo. Ad esempio, eseguirò una ricerca iniziale che controlla solo vm.SearchValue. Questo mi darà un elenco di film e opzioni per filtrare e ordinare. Quindi quando seleziono di filtrare per Audio o Media, ecc., Ottengo il problema.
Ecco la traccia dello stack:
at lambda_method(Closure , InternalEntityEntry )
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.SimpleNonNullableDependentKeyValueFactory`1.TryCreateFromCurrentValues(InternalEntityEntry entry, TKey& key)
at Microsoft.EntityFrameworkCore.Query.Internal.WeakReferenceIdentityMap`1.CreateIncludeKeyComparer(INavigation navigation, InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.IncludeCore(Object entity, INavigation navigation)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.Include(QueryContext queryContext, Object entity, IReadOnlyList`1 navigationPath, IReadOnlyList`1 relatedEntitiesLoaders, Int32 currentNavigationIndex, Boolean queryStateManager)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.Include(QueryContext queryContext, Object entity, IReadOnlyList`1 navigationPath, IReadOnlyList`1 relatedEntitiesLoaders, Boolean queryStateManager)
at Microsoft.EntityFrameworkCore.Query.Internal.GroupJoinInclude.GroupJoinIncludeContext.Include(Object entity)
at Microsoft.EntityFrameworkCore.Query.Internal.GroupJoinInclude.GroupJoinIncludeContext.Include(Object entity)
at Microsoft.EntityFrameworkCore.Query.Internal.GroupJoinInclude.GroupJoinIncludeContext.Include(Object entity)
at Microsoft.EntityFrameworkCore.Query.Internal.GroupJoinInclude.GroupJoinIncludeContext.Include(Object entity)
at Microsoft.EntityFrameworkCore.Query.QueryMethodProvider.<_GroupJoin>d__26`4.MoveNext()
at System.Linq.Enumerable.<SelectManyIterator>d__165`3.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.<_TrackEntities>d__15`2.MoveNext()
at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source, Int32& length)
at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
at System.Linq.SystemCore_EnumerableDebugView`1.get_Items()
Immagini qui sotto:
AGGIORNAMENTO: questo è stato risolto. C'è stato un altro controllo che ha coinvolto l'utente dell'applicazione che stava causando una valutazione lato client, che è stato spostato e ora la query funziona come previsto.
Questa risposta è fuori dal comune ed è un po 'di congetture da parte mia, quindi mi scuso se non è utile.
Ad ogni modo, un paio di cose mi stanno emergendo.
Innanzitutto, la tua funzione WhereIf () - non sta proprio facendo ciò che farebbe Where (). Where () prende una fonte e restituisce una seconda fonte in cui il recordset viene riconosciuto. In particolare, non cambia affatto l'origine dati originale. Bene, WhereIf () sta provando a farlo - sta cambiando la variabile 'source' che viene passata alla funzione. Ho cercato su Google e IQueryable non sembra immutabile, il che significa che può essere modificato senza creare una nuova istanza di classe, quindi non sono sicuro che questa riga di codice non stia rovinando le fondamenta :
source = source.Where(predicate);
... spiegherebbe i risultati che stai ottenendo. Il primo "WhereIf" con una condizione vera funziona, ma il successivo no - perché il primo ha fatto un casino con l'oggetto base su cui stava lavorando. Per lo meno, dovresti cambiarlo in 'return source.Where (predicato)', semplicemente per chiarezza del codice (dal momento che il tuo codice esistente fa sembrare che stia cercando di cambiarlo.)
In secondo luogo, hai provato a rompere l'affermazione? Voglio dire, qualcosa del genere:
var results = SomeLinq.SomeStatement(a => something(a))
.Where(b => b == something)
.Where(c => c == something)
... è la stessa cosa di:
var mainQueryable = SomeLinq.SomeStatement(a => something(a));
var filtered = mainQueryable.Where(b => b == something);
var results = filtered.Where(c => c == something);
Che a sua volta ti permetterebbe di semplificare l'immagine verso il basso per LINQ:
IQueryable<Film> films = _context.Films
.Include(f => f.Media)
.Include(f=> f.Audio)
.Include(f => f.FilmGenres)
.ThenInclude(fg => fg.Genre);
if (!string.IsNullOrEmpty(vm.SearchValue)) films = films.Where(f => f.Equals(vm.SearchValue, StringComparison.OrdinalIgnoreCase);
if (!string.IsNullOrEmpty(vm.MediaFilter)) films = films.Where(f => f.Media.Name == vm.MediaFilter);
// etc...
... quindi l'istruzione LINQ finale non ha clausole WHERE superflue che in realtà non filtrano nulla.
Ad ogni modo, spero che questo ti aiuti un po '.