c# - 创建一个动态 EF 过滤器,为任何字符串实体属性构建一个 LINQ Where equals/contains 语句
问题描述
简而言之,我正在寻找这个人所做的事情,但是使用 Entity Framework 6。
实施建议的解决方案会导致错误“LINQ to Entities 不支持 LINQ 表达式节点类型 'Invoke'”。由于建议的解决方案使用Invoke
,这显然是一个问题。
我知道有一种方法可以利用自定义 Compose 方法来重写表达式树而不使用Invoke
,但我似乎无法理解它。
这就是我想要写的。
我IQueryable<TEntity>
使用一个对象动态构建了一个对象,该QueryParameters
对象只是用于 WHERE 子句的一组属性。TEntity
是一个标准的代码优先 EF 实体,到处都有数据注释。查询构造如下所示:
IQueryable<TEntity> query = Context.Set<TEntity>();
if (queryParams == null)
return query;
if (!string.IsNullOrWhiteSpace(queryParams.FirstName))
{
if (queryParams.ExactSearch)
{
query = query.Where(x => x.FirstName == queryParams.FirstName);
}
else
{
if (queryParams.PreferStartsWith)
{
query = query.Where(
x => x.FirstName.ToLower()
.StartsWith(
queryParams.FirstName
.ToLower()));
}
else
{
query = query.Where(
x => x.FirstName.ToLower()
.Contains(
queryParams.FirstName
.ToLower()));
}
}
}
// ... repeat for all of queryParams' string props.
// DateTime, int, bool, etc have their own filters.
对于要查询的字符串字段的每个查询参数,都会重复此操作。显然,这会导致大量重复代码。我希望能够编写一个带有这样签名的过滤器:
public static IQueryable<TEntity> Search<TEntity>(
this IQueryable<TEntity> query,
Expression<Func<TEntity, string>> fieldExpression,
string searchValue,
bool exactSearch = true,
bool useStartsWithOverContains = false) {...}
然后我可以像这样消费:
if (!string.IsNullOrWhiteSpace(queryParams.FirstName))
{
query = query.Search(
x => x.FirstName,
queryParams.FirstName,
queryParams.ExactSearch,
queryParams.PreferStartsWith);
}
最接近我对该扩展方法的定义如下,但如前所述,它会产生“LINQ to Entities 不支持'Invoke'”错误:
public static IQueryable<TEntity> Search<TEntity>(
this IQueryable<TEntity> query,
Expression<Func<TEntity, string>> fieldExpression,
string searchValue,
bool exactSearch = true,
bool useStartsWithOverContains = false)
{
if (string.IsNullOrWhiteSpace(searchValue))
return query;
searchValue = searchValue.Trim();
Expression<Func<TEntity, bool>> expression;
if (exactSearch)
{
var x = Expression.Parameter(typeof(TEntity), "x");
var left = Expression.Invoke(fieldExpression, x);
var right = Expression.Constant(searchValue);
var equalityExpression = Expression.Equal(left, right);
expression = Expression.Lambda<Func<TEntity, bool>>(
equalityExpression,
x);
}
else
{
searchValue = searchValue.ToLower();
var x = Expression.Parameter(typeof(TEntity), "x");
var fieldToLower = Expression.Call(
Expression.Invoke(fieldExpression, x),
typeof(string).GetMethod(
"ToLower",
Type.EmptyTypes));
var searchValueExpression =
Expression.Constant(searchValue);
var body = Expression.Call(
fieldToLower,
typeof(string).GetMethod(
useStartsWithOverContains ? "StartsWith" : "Contains",
new[] { typeof(string) }),
searchValueExpression);
expression = Expression.Lambda<Func<TEntity, bool>>(
body,
x);
}
return query.Where(expression);
}
我开始包含我提到的Compose 方法,但我很快就迷路了,因此将其删除。
接受任何指导!谢谢!
解决方案
编写表达式比每次都尝试手动构造表达式要容易得多。它的编写速度更快,更不容易出错,并且实际上最终得到了您可以在其末尾实际阅读的代码。您需要做的就是编写代码,说明如何使用组合表达式中的值,您已经从原始代码中获得了这些值。
public static IQueryable<TEntity> Search<TEntity>(
this IQueryable<TEntity> query,
Expression<Func<TEntity, string>> fieldExpression,
string searchValue,
bool exactSearch = true,
bool useStartsWithOverContains = false)
{
if (string.IsNullOrWhiteSpace(searchValue))
return query;
searchValue = searchValue.Trim();
if (exactSearch)
{
return query.Where(fieldExpression.Compose(field => field == searchValue));
}
else if (useStartsWithOverContains)
{
return query.Where(fieldExpression.Compose(field => field.StartsWith(searchValue.ToLower())));
}
else
{
return query.Where(fieldExpression.Compose(field => field.Contains(searchValue.ToLower())));
}
}
请注意,您可能应该使用“比较”或类似的枚举,而不是使用两个布尔值。例如,现在有人可以说他们不想完全确定,但他们确实想使用开头。三个选项中只有一个参数。
推荐阅读
- java - 如果 URL 包含查询字符串参数,则屏幕截图在 selenium-chromedriver 中失败
- azure - Hive View 对 HDInsight 4.0 的本机支持
- reactjs - 如何为注册过程创建激活码?
- python - 从另一个数组的子集创建一个新数组
- linux - Netbeans 在 Linux 上构建 Ant 项目
- reactjs - 如何为钩子编写测试?
- .net - 使用 cyper 获取 neo4j 的内部唯一 ID 并将其分配给我的对象
- typescript - 配置 TSLint 以显示静态只读名称错误:'some_string'
- javascript - 检查时间戳是否超过 24 小时 - Javascript
- android - 如何在android中从SQL lite数据库存储和检索动态添加的图像