我正在重构一个项目并回到过去我从未解决过的问题。我试图在EF Core数据库的查询上执行多个过滤器。
在过去,我曾尝试设置一系列Where语句,检查过滤器语句是否为null或匹配过滤器。
这在查询中的某处返回了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异常。
我已经尝试逐行完成每一行(电影=电影。其中(...))。我试过跳过WhereIf,只是做if语句和标准Where,而且所有这些都有相同的结果。
只有当我创建一个List对象时,由一般的数据查询填充,然后过滤我使其工作的List对象。
那么,在EF Core中过滤IQueryable有什么问题?这是不允许的,还是我做错了什么?
更新:所有电影对象都有媒体/音频/ FilmGenre对象,所有内容都包含在内。我已经验证了IQueryable源中的项目在WhereIf方法中的Where语句之前具有所有这些项目。
我已经尝试单独分离每个过滤器语句,包括跳过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()
图片如下:
更新:这已经解决了。还有一个涉及应用程序用户的检查,该检查导致客户端评估,已移动,现在查询按预期工作。
我总是只使用一个简单的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)));
这个答案不在袖口,对我来说有点猜测,所以如果它没有用,我道歉。
无论如何,有几件事情对我很有帮助。
首先,你的WhereIf()函数 - 它不是像Where()那样做的。 Where()接受一个源并返回第二个源,其中记录集被剔除。值得注意的是,它根本不会改变原始数据源。好吧,你的WhereIf()试图这样做 - 它正在改变传递给函数的'source'变量。我做了一些谷歌搜索,并且IQueryable看起来不像是不可变的,这意味着它可以在不创建新的类实例的情况下进行更改,所以我并不认为这行代码不会搞砸它的基础。 :
source = source.Where(predicate);
......它会解释你得到的结果。第一个具有真实条件的'WhereIf'可以工作,但是随后的那个没有 - 因为第一个与它正在处理的基础对象混淆了。至少,您应该将其更改为'return source.Where(predicate)',只是为了清晰代码(因为您的现有代码使其看起来像是在尝试更改它。)
第二,你试过破坏声明吗?我的意思是,像这样:
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子句,实际上没有过滤任何东西。
无论如何,希望这些帮助有点。