Почему я не могу отфильтровать IQueryable как со списком? - PullRequest
0 голосов
/ 10 апреля 2019

Я немного рефакторинг проекта и вернулся к проблеме, которую я никогда не решал в прошлом. Я пытаюсь выполнить несколько фильтров по запросу базы данных EF Core.

В прошлом я пытался настроить серию операторов Where, которые проверяли, был ли оператор фильтра пустым ИЛИ, сопоставляя фильтр.

Это вернуло исключение nullReferenceException где-то в запросе. Я решил эту проблему, запустив свой запрос без фильтров, а затем применив фильтры к моему списку.

Я вернулся и создал расширение WhereIf и надеялся, что оно, возможно, решит мои проблемы, а также сделает код немного чище, но та же проблема всплывает.

В настоящее время у меня есть четыре фильтра, которые я пытаюсь запустить в запросе, и он отлично проходит исходный фильтр, но если выбран какой-либо из трех других фильтров, запрос имеет исключение nullReferenceException.

Это снова работает, если я получаю список из общего запроса и первого фильтра, а затем впоследствии применяю фильтры к моему списку.

Вот что я хотел бы сделать:

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));

Вот метод 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;
        }

Фильтр на vm.SearchValue проходит нормально, и когда я перехожу через него, значение IQueryable, как и ожидалось. Как только он попадает в любой из других фильтров, он возвращается с nullReferenceException (когда он, наконец, попадает в ToList () позже). Если я посмотрю на значение source до возврата, оно показывает, что в Result View есть нулевое исключение.

Я пробовал делать каждую строку одну за другой (с фильмом = фильм. Где (...)). Я попытался пропустить WhereIf и просто сделать if и стандартное Where, и все это дает тот же результат.

Только когда я создаю объект List, заполняемый общим запросом данных, а затем фильтрую этот объект List, я заставляю его работать.

Итак, в чем проблема с фильтрацией на IQueryable в EF Core? Это не разрешено, или я что-то не так делаю?

Обновление: все объекты Film имеют объекты Media / Audio / FilmGenre, и все включено. И я проверил, что элементы в источнике IQueryable имеют все эти элементы до оператора Where в методе WhereIf.

Я попытался отделить каждый оператор фильтра отдельно, и это включает в себя пропуск метода WhereIf и использование операторов if.

Кроме того, одновременно можно выбрать только один фильтр (на данный момент). Те, которые не выбраны, приводят к тому, что условие ложно и проблем нет. Он работает только при работе с активным фильтром. Например, я сделаю начальный поиск, который проверяет только vm.SearchValue. Это даст мне список фильмов и опций для фильтрации и сортировки. Затем, когда я выбираю фильтр по аудио или медиа и т. Д., Я получаю проблему.

Вот трассировка стека:

   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()

Изображения ниже:

  1. Вот представление результатов источника при прохождении фильтра SearchValue до оператора Where в WhereIf
  2. Вот оно после того, где утверждение
  3. Здесь он проходит через AudioFilter - показан предикат.
  4. Вот источник до оператора Where при выполнении AudioFilter - такой же, как после фильтрации SearchValue
  5. И, наконец, после выполнения команды Where при выполнении фильтрации аудио

Here is source's Results View when going through the SearchValue filter prior to the Where statement in WhereIf

Here it is after that Where statement

Here it's going through the AudioFilter - predicate shown.

Here is the source prior to the Where statement when doing the AudioFilter - same as after the SearchValue filtering

And finally, after doing the Where when doing the Audio filtering

ОБНОВЛЕНИЕ: это было решено. Была еще одна проверка с участием пользователя приложения, которая вызывала оценку на стороне клиента, которая была перемещена, и теперь запрос работает как предполагалось.

Ответы [ 2 ]

0 голосов
/ 11 апреля 2019

Этот ответ не совсем уместен и с моей стороны немного догадывается, поэтому я прошу прощения, если он не поможет.

В любом случае, пара вещей стоит передо мной.

Сначала, ваша функция WhereIf () - это не совсем то, что сделал бы Where ().Where () берет источник и возвращает второй источник, где набор записей перезаписан.Примечательно, что он не меняет исходного источника данных вообще.Что ж, ваш WhereIf () пытается это сделать - он изменяет переменную 'source', которая передается в функцию.Я немного погуглил, и IQueryable не не выглядит как неизменяемый, что означает, что его можно изменить без создания нового экземпляра класса, поэтому я не уверен, что эта строка кода не испортилафундамент, на котором строится:

source = source.Where(predicate);

... это объясняет результаты, которые вы получаете.Первый «WhereIf» с истинным условием работает, но следующий - нет, потому что первый из них перепутан с базовым объектом, над которым он работал.По крайней мере, вы должны изменить его на «return source.Where (предикат)», просто для ясности кода (поскольку ваш существующий код делает его похожим на , пытающийся изменить его.)

Во-вторых, вы пытались разорвать утверждение?Я имею в виду, что-то вроде этого:

var results = SomeLinq.SomeStatement(a => something(a))
        .Where(b => b == something)
        .Where(c => c == something)

... это то же самое, что и:

var mainQueryable = SomeLinq.SomeStatement(a => something(a));
var filtered = mainQueryable.Where(b => b == something);
var results = filtered.Where(c => c == something);

, что, в свою очередь, позволит вам упростить картину для 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...

... так что в последнем утверждении LINQ нет лишних предложений WHERE, которые на самом деле ничего не фильтруют.

В любом случае, надеюсь, они немного помогут.

0 голосов
/ 10 апреля 2019

Я всегда просто использую простой оператор OR вместо WhereIf

 IQueryable<Film> films = _context.Films
    .Include(x => x.Media)
    .Include(x => x.Audio)
    .Include(x => x.FilmGenres)
    .ThenInclude(g => g.Genre)
                .Where(f => string.IsNullOrEmpty(vm.SearchValue) || f.Name.ToLower().Contains(vm.SearchValue.ToLower()))
                .Where(f => string.IsNullOrEmpty(vm.MediaFilter) || f.Media.Name == vm.MediaFilter)
                .Where(f => string.IsNullOrEmpty(vm.AudioFilter) || f.Audio.Name == vm.AudioFilter)
                .Where(f => string.IsNullOrEmpty(vm.GenreFilter) || (f.FilmGenres.Any(fg => fg.Genre != null && fg.Genre.Name == vm.GenreFilter)));
...