没有 PredicateBuilder 的 LINQ 中的动态 OR

yes*_*man 5 c# linq

我正在构建一种方法,该方法采用一个或多个条件来使用 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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

这具有的效果是,如果我查找一个 Id 为 1 的对象和一个 Id 为 2 的对象,我将一无所获,因为没有行同时具有 1 和 2 的 Id。所以我需要一个 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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

我的代码与页面略有不同,因为我将表达式传递到方法中,然后将其传递到 Predicate.Or 方法中。

上面的方法The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.报错。这是有道理的,因为实体框架不知道如何将此代码转换为有效的查询。

链接站点上的解决方案是下载他们的 Nuget 包或源代码,这使得该代码可以工作。但是,我真的不太愿意为单个函数放入数百行未知且看似未经测试的代码,在我看来,这些代码早在 Microsoft 很久以前就应该内置到 LINQ 中。我项目的首席开发人员过去也强烈建议不要使用不是直接来自 Microsoft 的未知包。我正在处理敏感信息,所以我宁愿安全也不要抱歉。

所以,我的问题是:有没有办法在 LINQ 中获得 OR 函数而不必使用外部 Nuget 包?

Iva*_*oev 4

正如我在评论中提到的,您可以使用Universal PredicateBulder或我的答案中的类在 linq toEntity 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

它又使用这个助手:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在示例方法可以很简单:

public ICollection<PhotoFile> FindAllBy(params Expression<Func<PhotoFile, bool>>[] criteria)
{
    using (var ctx = new CotanContext())
        return ctx.PhotoFiles.WhereAny(criteria).ToList();
}
Run Code Online (Sandbox Code Playgroud)