【问题标题】:Dynamic OR in LINQ without the PredicateBuilder没有 PredicateBuilder 的 LINQ 中的动态 OR
【发布时间】:2016-06-29 17:25:27
【问题描述】:

我正在构建一种方法,该方法采用一个或多个条件来使用 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 包?

【问题讨论】:

  • 您真的需要将子句传递到您的函数中吗?为什么不返回一个 IQueryable 并让调用者弄清楚该怎么做?
  • 至少在第一种方法中,这就是我正在做的。我只在调用 ToList() 时将 IQueryable 转换为 IEnumerable。我使用接受表达式并返回另一个 IQueryable 的 Where() 重载。而第二个示例中的 Or 方法只返回另一个 Expression,因此也没有数据库调用。
  • 感谢 Ivan,这很简单,我可以为它编写自己的测试。它也足够短,其他人也可以查看。
  • @ohyeah 不,我的意思是,从您的存储库返回 IQueryable 并允许服务应用自己的过滤器。您在这里所做的基本上是阻止 fluent API 发挥作用。

标签: c# linq


【解决方案1】:

正如我在 cmets 中提到的,您可以使用 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();
}

【讨论】:

    【解决方案2】:

    简短的回答:是的。如果您查看 PredicateBuilder 代码,它使用表达式来构造您的查询。因此,您可以尝试重新创建该行为,但正如以前所做的那样,为什么不尝试修复您的代码?

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

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

    【讨论】:

    • 这也是我的第一个想法(伟大的思想都一样:-))。但是,AsExpandable() 扩展也有其他几个依赖,而这些依赖又是有依赖的,这迫使我如果真的想使用它,就不得不复制大量的源代码。
    • 您担心的依赖关系是什么?基于 LinqKit Nuget 包,依赖项是相当常见的程序集。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-15
    相关资源
    最近更新 更多