繁体   English   中英

没有PredicateBuilder的LINQ中的动态OR

[英]Dynamic OR in LINQ without the PredicateBuilder

我正在构建一个方法,该方法采用一个或多个标准来使用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();
        }
    }

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

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

链接站点上的解决方案是下载他们的Nuget包或源代码,这使得此代码可以正常工作。 但是,我觉得在单个函数中放入数百行未知和看似未经测试的代码并不是很舒服,在我看来,微软应该已经在LINQ中构建了这些代码。 我的项目的主要开发人员过去也强烈反对使用不直接来自Microsoft的未知软件包。 我正在处理敏感信息,所以我宁愿安全而不是抱歉。

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

正如我在评论中提到的,您可以使用Universal PredicateBulder或我的答案中的类来建立linq到实体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代码,它会使用表达式来构造查询。 所以你可以尝试重新创建这种行为,但是之前已经做过,为什么不尝试修复你的代码呢?

我在我的存储库类中经常使用PredicateBuilder,我记得有同样的问题。 我通过在每次调用Entity Framework时添加AsExpandable()(在LinqKit命名空间中)解决了这个问题:

return ctx.PhotoFiles.AsExpandable().Where(predicate).ToList();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM