我正在为我的实体框架应用程序编写一个简单的搜索查询。 我需要检查一堆字段是否为空,如果不是,请对它们调用 ToLower(( 并与搜索查询进行比较。 LINQ 查询如下所示:
public IQueryable<Store> SearchStores(string q, IQueryable<Store> source)
{
q = q.ToLower();
return (
from s in source
where (
(s.Name != null && s.Name.ToLower().Contains(q)) ||
(s.Description != null && s.Description.ToLower().Contains(q)) ||
...
}
像这样的行很多,所以我很想写一个辅助方法来清理一下:
public static bool SafeSearch(this string s, string q)
{
return s == null ? false : s.ToLower().Contains(q);
}
但是,这当然不起作用,因为 LINQ to 实体不了解安全搜索功能是什么:
LINQ to Entities 无法识别方法"布尔安全搜索(System.String,System.String("方法,并且此方法无法转换为存储表达式。
有没有一种简单的方法来编写这样的简单自定义函数?
谢谢!
由于 linq 使用的表达式在实际调用数据库之前不会执行,因此您需要将函数包装在谓词中。
private static Func<Country, bool> Predicate(string q)
{
return x => (
q.SafeSearch(x.Name) ||
q.SafeSearch(x.Description)
);
}
此外,通过在查询时调用安全搜索扩展程序来反转它,将处理 x.Name 为 null 的情况。
public static class SearchExt
{
public static bool SafeSearch(this string q, string param)
{
return param == null ? false : param.ToLower().Contains(q);
}
}
然后你可以把它与扩展方法一起使用
return source.Where(Predicate(q));
或使用 LINQ 表达式
return from p in source
where Predicate(q).Invoke(p)
select p;
有一种方法可以准备动态查询和条件,也可以使用函数来构建其中的一部分。语法也是可读的,这将适用于问题的"简单"部分。这可以通过组合 Linq 表达式来实现。有几篇关于如何做到这一点的文章,但我想我想出了一种新的方法。至少我没有在网上找到它。
要继续,您需要一个包含 3 个简单函数的库。他们使用System.Linq.Expressions.ExpressionVisitor
来动态修改表达式。关键功能是统一表达式中的参数,以便使 2 个同名参数相同 (UnifyParametersByName
(。剩下的部分是用给定的表达式(ReplacePar
(和辅助方法(NewExpr
(替换命名参数。该库在 github:LinqExprHelper 上提供 MIT 许可证,但您可以自己快速编写一些内容。
首先定义一些方法,这些方法稍后可用于创建动态查询。
public class Store
{
...
public static Expression<Func<Store, bool>>
SafeSearchName(string sWhat)
{
return LinqExprHelper.NewExpr(
(Store s) => s.Name != null && s.Name.ToLower().Contains(sWhat)
);
}
public static Expression<Func<Store, bool>>
SafeSearchDesc(string sWhat)
{
return LinqExprHelper.NewExpr(
(Store s) => s.Description != null && s.Description.ToLower().Contains(sWhat)
);
}
}
然后以这种方式查询:
// Define a master condition, using named parameters.
var masterExpr = LinqExprHelper.NewExpr(
(Store s, bool bSearchName, bool bSearchDesc)
=> (bSearchName && bSearchDesc));
// Replace stub parameters with some real conditions.
var combExpr = masterExpr
.ReplacePar("bSearchName", Store.SafeSearchName("b").Body)
.ReplacePar("bSearchDesc", Store.SafeSearchDesc("p").Body);
// Sometimes you may skip a condition using this syntax:
//.ReplacePar("bSearchDesc", Expression.Constant(true));
// It's interesting to see how the final expression looks like.
Console.WriteLine("expr: " + combExpr);
// Execute the query using combined expression.
db.Stores
.Where((Expression<Func<Store, bool>>)combExpr)
.ToList().ForEach(i => { Console.WriteLine(i.Name + ", " + i.Description); });
我还没有在生产中使用它,但一些简单的测试已经通过。我认为以这种方式组合查询没有任何限制。如果我们需要更多参数,我们可以附加额外的组合级别。这种方法的优点是您可以使用内联 lambda 表达式,这些表达式很好读,以及动态表达式创建和组合,这是非常有能力的。
它到底"简单"吗?如果您认为 Linq 的方法语法很简单,那么这几乎就是这么简单。它不允许您创建自定义 Linq 函数,但为您提供了类似的功能。