[英]EF Core - Expression Tree Equivalent for IQueryable Search
I have an initial workflow put together that allows me to perform inclusive searches for string properties of objects contained in an IQueryable
:我有一个初始工作流放在一起,它允许我对
IQueryable
包含的对象的字符串属性执行包含搜索:
public static IQueryable ApplySearch(this IQueryable queryable, string search)
{
// validation omitted for brevity
var expression = queryable
.Cast<object>()
.Where(item => item.SearchStringTree(search))
.Expression;
var result = queryable.Provider.CreateQuery(expression);
return result;
}
static bool SearchStringTree<T>(this T value, string search) =>
value.GetObjectStrings().Any(s => s.Contains(search.ToLower()));
static IEnumerable<string> GetObjectStrings<T>(this T value)
{
var strings = new List<string>();
var properties = value.GetType()
.GetProperties()
.Where(x => x.CanRead);
foreach (var prop in properties)
{
var t = prop.PropertyType.ToString().ToLower();
var root = t.Split('.')[0];
if (t == "system.string")
{
strings.Add(((string)prop.GetValue(value)).ToLower());
}
else if (!(root == "system"))
{
strings.AddRange(prop.GetValue(value).GetObjectStrings());
}
}
return strings;
}
Would it be possible to apply this concept in a way that Entity Framework can translate prior to DbContext execution?是否有可能以 Entity Framework 可以在 DbContext 执行之前转换的方式应用这个概念?
I've been looking into potentially using Expression Trees to accomplish this.我一直在研究可能使用表达式树来实现这一点。
Here's a working Repl.it showing the IQueryable
implementation above.这是一个工作Repl.it,显示了上面的
IQueryable
实现。
You definitely need to build expression tree, basically multi or
(C# ||
) predicate expression for all (nested) string
properties.您肯定需要为所有(嵌套)
string
属性构建表达式树,基本上是 multi or
(C# ||
) 谓词表达式。
Something like this (expression version of your code):像这样(代码的表达式版本):
public static class FilterExpression
{
public static IQueryable<T> ApplySearch<T>(this IQueryable<T> source, string search)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (string.IsNullOrWhiteSpace(search)) return source;
var parameter = Expression.Parameter(typeof(T), "e");
// The following simulates closure to let EF Core create parameter rather than constant value (in case you use `Expresssion.Constant(search)`)
var value = Expression.Property(Expression.Constant(new { search }), nameof(search));
var body = SearchStrings(parameter, value);
if (body == null) return source;
var predicate = Expression.Lambda<Func<T, bool>>(body, parameter);
return source.Where(predicate);
}
static Expression SearchStrings(Expression target, Expression search)
{
Expression result = null;
var properties = target.Type
.GetProperties()
.Where(x => x.CanRead);
foreach (var prop in properties)
{
Expression condition = null;
var propValue = Expression.MakeMemberAccess(target, prop);
if (prop.PropertyType == typeof(string))
{
var comparand = Expression.Call(propValue, nameof(string.ToLower), Type.EmptyTypes);
condition = Expression.Call(comparand, nameof(string.Contains), Type.EmptyTypes, search);
}
else if (!prop.PropertyType.Namespace.StartsWith("System."))
{
condition = SearchStrings(propValue, search);
}
if (condition != null)
result = result == null ? condition : Expression.OrElse(result, condition);
}
return result;
}
}
The non generic version is not much different - just instead of Where
extension method you need to generate a "call" to it in the query expression tree:非通用版本没有太大不同 - 只是代替
Where
扩展方法,您需要在查询表达式树中生成对它的“调用”:
public static IQueryable ApplySearch(this IQueryable source, string search)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (string.IsNullOrWhiteSpace(search)) return source;
var parameter = Expression.Parameter(source.ElementType, "e");
var value = Expression.Property(Expression.Constant(new { search }), nameof(search));
var body = SearchStrings(parameter, value);
if (body == null) return source;
var predicate = Expression.Lambda(body, parameter);
var filtered = Expression.Call(
typeof(Queryable), nameof(Queryable.Where), new[] { source.ElementType },
source.Expression, Expression.Quote(predicate));
return source.Provider.CreateQuery(filtered);
}
While this works, it's not much useful because all LINQ extensions methods (including AsEnumerable(),
ToList()` etc.) work with generic interface.虽然这有效,但它没有多大用处,因为所有 LINQ 扩展方法(包括
AsEnumerable(),
ToList()` 等)都使用通用接口。
Also in both cases, the type of the query element must be known in advance, eg T
in the generic version, query.ElementType
in the non generic version.同样在这两种情况下,查询元素的类型必须事先知道,例如通用版本中的
T
,非通用版本中的query.ElementType
。 This is because expression tree are processed in advance, when there are no "objects", hence it can't use item.GetType()
.这是因为表达式树是预先处理的,当没有“对象”时,因此它不能使用
item.GetType()
。 For the same reason, IQueryable
translators like EF Core don't like Cast
"calls" inside the query expression tree.出于同样的原因,像 EF Core 这样的
IQueryable
翻译器不喜欢在查询表达式树中Cast
“调用”。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.