LINQ过滤器结合精确匹配,如SQL IN和StartsWith匹配

时间:2016-06-19 10:20:26

标签: c# linq nhibernate

我有产品实体:

public class Product : DomainBase
{
    public virtual string Name { get; set; }
}

应该有选择按过滤器选择产品的选项,过滤器包含一系列名称,如:

public static IEnumerable<Product> SearchArrayQueryLinq(IEnumerable<string> names)
    {
        using (var session = Database.OpenSession())
        {
            var products = session.Query<Product>();

            var result = products.Where(product => names.Any(name =>  product.Name.Contains(name)));

            return result.ToList();
        }
    }

但它会抛出

  

System.NotSupportedException:不支持指定的方法。

完成此类过滤的正确方法是什么?

3 个答案:

答案 0 :(得分:2)

如果您不了解更多关于您正在连接的数据库或什么库(RavenDB ......已经快速完成了Google?),那么很难完全确定问题是什么。

然而,我认为正在发生的事情是你要表达对IQueryable&#34; Where&#34;扩展方法和库正在尝试将其转换为搜索条件以针对数据库运行..并且失败,因为&#34;任何&#34;这样的嵌套标准不支持(再次,我猜测)。

可能会或可能不会被翻译成数据库语言的LINQ表达式(例如SQL)因执行翻译的库而异,并且因所谈论的数据库而异。

例如,以下(基本上是您想要做的)适用于Entity Framework:

    private static void Test(IEnumerable<string> names)
    {
        using (var context = new NORTHWNDEntities())
        {
            foreach (var product in context.Products.Where(product => names.Any(name => product.ProductName.Contains(name))))
            {
                Console.WriteLine(product.ProductName);
            }
        }
        Console.ReadLine();
    }

一个简单的选择是将代码更改为

public static IEnumerable<Product> SearchArrayQueryLinq(IEnumerable<string> names)
{
    using (var session = Database.OpenSession())
    {
        var products = session.Query<Product>();
        return result = products.ToList().Where(product => names.Any(name =>  product.Name.Contains(name)));
    }
}

这应该有效..但是,它将从数据库中获取所有产品并在内存中执行过滤。这比使数据库执行搜索效率低。

另一种方法是生成&#34;表达式&lt; Func&lt; Product,bool&gt;&gt;&#34;过滤自己,这对于您用来翻译的库来说更容易。相反,如果是嵌套的&#34;任何&#34;标准,您可以生成一组简单的&#34; OR&#34;名称检查然后有一个更好的工作变化。以下将实现这一点 - 但它的代码相当多。如果您需要在多个地方执行此操作,则可以使某些代码更加通用并重复使用。

    private static IEnumerable<Product> SearchArrayQueryLinq(IEnumerable<string> names)
    {
        using (var context = new NORTHWNDEntities())
        {
            return context.Products.Where(GetCombinedOrFilter(names)).ToList();
        }
    }

    private static Expression<Func<Product, bool>> GetCombinedOrFilter(IEnumerable<string> names)
    {
        var filter = GetNameFilter(names.First());
        foreach (var name in names.Skip(1))
            filter = CombineFiltersAsOr(filter, GetNameFilter(name));
        return filter;
    }

    private static Expression<Func<Product, bool>> GetNameFilter(string name)
    {
        return product => product.ProductName.Contains(name);
    }

    private static Expression<Func<Product, bool>> CombineFiltersAsOr(Expression<Func<Product, bool>> x, Expression<Func<Product, bool>> y)
    {
        // Combine two separate expressions into one by combining as "Or". In order for this to work, instead of there being a parameter
        // for each expression, the parameter from the first expression must be shared between them both (otherwise things will go awry
        // when this is translated into a database query) - this is why ParameterRebinder.ReplaceParameters is required.
        var expressionParameter = x.Parameters.Single();
        return Expression.Lambda<Func<Product, bool>>(
            Expression.Or(x.Body, ParameterRebinder.ReplaceParameters(y.Body, toReplace: y.Parameters.Single(), replaceWith: expressionParameter)),
            expressionParameter
        );
    }

    // Borrowed and tweaked from https://blogs.msdn.microsoft.com/meek/2008/05/02/linq-to-entities-combining-predicates/
    public sealed class ParameterRebinder : ExpressionVisitor
    {
        public static Expression ReplaceParameters(Expression expression, ParameterExpression toReplace, ParameterExpression replaceWith)
        {
            return new ParameterRebinder(toReplace, replaceWith).Visit(expression);
        }

        private readonly ParameterExpression _toReplace, _replaceWith;
        private ParameterRebinder(ParameterExpression toReplace, ParameterExpression replaceWith)
        {
            _toReplace = toReplace;
            _replaceWith = replaceWith;
        }

        protected override Expression VisitParameter(ParameterExpression p)
        {
            if (p == _toReplace)
                p = _replaceWith;
            return base.VisitParameter(p);
        }
    }

更新:我没注意到你的nhibernate标签 - 哎呀!使用nhibernate所具有的组合方法的标准可能比所有这些更容易.. :)我会评论你的答案而不是更新我的答案但我还没有得到必要的50个代表..

答案 1 :(得分:0)

您正在尝试混合使用两种条件并在字符串属性上应用IEnumerable方法。

您的查询应如下所示:

var result = products.Where(product => names.Contains(product.Name));

找到完全匹配。

对于完全匹配和StartsWith的组合,它应如下所示:

var results = products.Where(product => (names.Contains(product.Name) || names.Any(name => name.StartsWith(product.Name))));

答案 2 :(得分:0)

当我深入了解NHibenrate文档时,它包含CriteriaAPI,所以我想到了这个

            using (var session = Database.OpenSession())
            {
                var products = session.CreateCriteria<Product>();

                if (names == null)
                {
                    return products.List<Product>();
                }

                var orClause = Expression.Disjunction();

                foreach (var name in names)
                {
                    orClause.Add(Expression.Like(nameof(Product.Name), name, MatchMode.Start));
                }

                products.Add(orClause);

                return products.List<Product>();
            }