为什么我不能像使用列表一样过滤IQueryable?

时间:2019-04-10 18:34:33

标签: c# asp.net-mvc linq entity-framework-core iqueryable

我正在对项目进行一些重构,并且回到了以前从未解决过的问题。我正在尝试对EF Core数据库的查询执行多个过滤器。

过去,我曾尝试设置一系列Where语句,这些语句通过匹配过滤器来检查filter语句是否为空或。

这在查询中的某个地方返回了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的值,则表明结果视图中具有null异常。

我已经尝试了每一行的内容(电影= film.Where(...))。我试图跳过WhereIf并只执行if语句和标准Where,所有这些都具有相同的结果。

仅当我创建一个List对象时,该对象由数据的常规查询填充,然后过滤该List对象,我才能使用它。

那么,在EF Core中对IQueryable进行过滤有什么问题?这是不允许的,还是我做错了什么?

更新:所有的Film对象确实具有Media / Audio / FilmGenre对象,并且所有内容都包括在内。而且我已经验证了IQueryable源中的项目在WhereIf方法中的Where语句之前具有所有这些项目。

我尝试过将每个过滤器语句分开,这包括跳过WhereIf方法和使用if语句。

此外,一次只能选择一个过滤器(目前)。那些未被选择的结果将导致条件为假,这没有问题。只有在使用有源滤波器时才会打ic。例如,我将进行一次初始搜索,仅检查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. 在WhereIf中的Where语句之前通过SearchValue过滤器时,这是源的结果视图
  2. 这是在where语句之后
  3. 这里要遍历AudioFilter-显示谓词。
  4. 这里是进行AudioFilter时Where语句之前的来源-与SearchValue过滤之后一样
  5. 最后,在执行“音频过滤”时执行“位置”之后

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 :(得分:0)

我总是只使用一个简单的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)));

答案 1 :(得分:0)

此答案已不存在,对我来说有点猜测,因此,如果没有帮助,我深表歉意。

无论如何,有几件事对我很重要。

首先,您的WhereIf()函数-它并没有完全执行Where()会做的事情。 where()获取一个源,并返回第二个源,记录集将在该第二个源中向下传播。值得注意的是,它根本不会更改原始数据源。好吧,您的WhereIf()试图做到这一点-它正在更改传递给函数的'source'变量。我进行了一些谷歌搜索,但IQueryable 并非看起来是不可变的,这意味着可以在不创建新类实例的情况下对其进行更改,因此,我对这行代码没有拧入建立基础的基础是:

source = source.Where(predicate);

...它将解释您得到的结果。第一个具有真实条件的“ WhereIf”有效,但后一个无效-因为第一个与正在处理的基础对象混淆。至少,您应该将其更改为“ return source.Where(predicate)”,仅出于代码清晰起见(因为您现有的代码使它看起来像它的 try 进行更改。)

第二,您是否尝试过将语句分解?我的意思是,像这样:

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子句,这些子句实际上并没有过滤掉任何内容。

无论如何,希望这些能有所帮助。