没有PredicateBuilder

时间:2016-06-29 07:54:56

标签: 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();
        }
    }

这样做的结果是,如果我寻找具有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包?

2 个答案:

答案 0 :(得分:4)

正如我在评论中提到的,您可以使用Universal PredicateBulder或我对Establish a link between two lists in linq to entities where clause的答案中的类。

但是,通过使用这个简单的扩展方法,您可以大大简化示例中的方法:

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();
}

答案 1 :(得分:0)

简短回答:是的。如果查看PredicateBuilder代码,它会使用表达式来构造查询。所以你可以尝试重新创建这种行为,但是之前已经完成了,为什么不试着修复你的代码?

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

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