我有产品实体:
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:不支持指定的方法。
完成此类过滤的正确方法是什么?
答案 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>();
}