简而言之,我打算使用实体框架6做what this guy did。
实施建议的解决方案会导致错误“ LINQ to Entities不支持LINQ表达式节点类型'Invoke'。” 由于建议的解决方案使用Invoke
,因此很明显一个问题。
我知道有一种方法可以利用a custom Compose method来重写表达式树而无需使用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 method,但是我很快就迷路了,因此将其删除。
打开任何指导!谢谢!
答案 0 :(得分:1)
通过构成表达式比通过每次尝试手动构造表达式要容易得多。编写起来更快,如此,出错的可能性要小得多,并且实际上以您可以在末尾实际读取的代码结束。您所需要做的就是编写代码,以了解如何在组合表达式中使用该值,而您原来的代码中已有该值。
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())));
}
}
请注意,您可能应该使用“比较”之类的枚举,而不要使用两个布尔值。例如,现在有人可以说他们不想要确切的确定,但是他们确实想使用始于。只需使用三个选项中的一个参数即可。