我正在构建一个方法,该方法采用一个或多个标准来使用LINQ查询数据库。我做了这个:
public ICollection<MyClass> FindAllBy(params Expression<Func<MyClass, bool>>[] criteria)
{
using (var ctx = new MyContext())
{
IQueryable<MyClass> result = ctx.MyClasses.AsNoTracking();
if (criteria != null && criteria.Length > 0)
{
foreach (var item in criteria)
{
result = result.Where(item);
}
}
return result.ToList();
}
}
这样做的效果是,如果我寻找Id为1和Id为2的对象,我一无所获,因为没有行同时具有Id为1和2。所以我需要一个OR子句。我找到了这个页面:
http://www.albahari.com/nutshell/predicatebuilder.aspx它有一个PredicateBuilder类,我用它来做这个:
public ICollection<PhotoFile> FindAllBy(params Expression<Func<PhotoFile, bool>>[] criteria)
{
using (var ctx = new CotanContext())
{
var predicate = PredicateBuilder.False<PhotoFile>();
if (criteria != null && criteria.Length > 0)
{
foreach (var item in criteria)
{
predicate = predicate.Or(item);
}
}
return ctx.PhotoFiles.Where(predicate).ToList();
}
}
我的代码与页面略有不同,因为我将表达式传递给方法,然后将其传递给谓词。或方法。
上面的方法给出了一个The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
错误。这是有意义的,因为实体框架不知道如何将此代码转换为有效的查询。
链接站点上的解决方案是下载他们的Nuget包或源代码,这使得这些代码可以工作。然而,我真的不愿意为一个函数放入几百行未知的、看似未经测试的代码,在我看来,这些代码早就应该被微软内置到LINQ中了。我项目的首席开发人员过去也强烈建议不要使用非直接来自微软的未知软件包。我在处理敏感信息,所以我宁愿安全也不愿后悔。
所以,我的问题:有没有办法在LINQ中获得OR函数,而不必使用外部Nuget包?
正如我在评论中提到的那样,您可以使用Universal predicatebuilder或我的答案中的类来在链接到实体where子句的两个列表之间建立链接。
但是,您可以通过使用这个简单的扩展方法来极大地简化方法,就像您的示例中的方法一样:
public static class QueryableExtensions
{
public static IQueryable<T> WhereAny<T>(this IQueryable<T> source, IEnumerable<Expression<Func<T, bool>>> predicates)
{
if (predicates == null || !predicates.Any()) return source;
var predicate = predicates.Aggregate((a, b) => Expression.Lambda<Func<T, bool>>(
Expression.OrElse(a.Body, b.Body.ReplaceParameter(b.Parameters[0], a.Parameters[0])),
a.Parameters[0]));
return source.Where(predicate);
}
}
,然后使用这个帮助器:
public static class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}
现在,示例方法可以像这样简单:
public ICollection<PhotoFile> FindAllBy(params Expression<Func<PhotoFile, bool>>[] criteria)
{
using (var ctx = new CotanContext())
return ctx.PhotoFiles.WhereAny(criteria).ToList();
}
简短的回答:是的。如果查看PredicateBuilder代码,就会发现它使用Expressions来构造查询。因此,您可以尝试重新创建该行为,但由于以前已经这样做了,为什么不尝试修复您的代码呢?
我在我的存储库类中使用了很多PredicateBuilder,我记得有同样的问题。我通过在每次对实体框架的调用中添加AsExpandable()(在LinqKit命名空间中)来解决这个问题:
return ctx.PhotoFiles.AsExpandable().Where(predicate).ToList();