Linq WHERE EF.Functions.Like - 为啥直接属性起作用而反射不起作用?
Posted
技术标签:
【中文标题】Linq WHERE EF.Functions.Like - 为啥直接属性起作用而反射不起作用?【英文标题】:Linq WHERE EF.Functions.Like - Why direct properties work and reflection does not?Linq WHERE EF.Functions.Like - 为什么直接属性起作用而反射不起作用? 【发布时间】:2020-02-07 18:32:00 【问题描述】:我尝试在数据库站点上执行一个简单的 LIKE 操作,同时提供基于泛型类型的查询构建服务。然而,我在调试时发现,使用反射执行 EF.Functions.Like()
不能按预期工作:
The LINQ expression 'where __Functions_0.Like([c].GetType().GetProperty("FirstName").GetValue([c], null).ToString(), "%Test%")' could not be translated and will be evaluated locally.
.
与众不同的代码
可行:
var query = _context.Set<Customer>().Where(c => EF.Functions.Like(c.FirstName, "%Test%"));
这会引发警告并尝试在内存中解决:
var query = _context.Set<Customer>().Where(c => EF.Functions.Like(c.GetType().GetProperty("FirstName").GetValue(c, null).ToString(), "%Test%"));
Linq 查询构建器或 EF.Functions 不支持反射吗?
对不起,如果问题看起来很基本,这是我第一次尝试使用 .NET Core :)
【问题讨论】:
这是可能的,但你不应该这样做。实体框架无法评估包含 SQL 反射的表达式。因此它会尝试将所有内容加载到内存中,然后执行表达式。你想达到什么目的?也许我们可以提供一个替代方案 在@Serdar 的评论中给出了更详细的答案。基本上 - 通过扩展 IQueryable 的通用查询参数搜索功能,其中queryParam.Key
是模型类属性,queryParam.Value
是 LIKE 子句中使用的模式。
【参考方案1】:
在 EF 中,lambdas 是 ExpressionTrees,表达式被转换为 T-SQL,以便 query 可以在数据库中执行。
你可以像这样创建一个扩展方法:
public static IQueryable<T> Search<T>(this IQueryable<T> source, string propertyName, string searchTerm)
if (string.IsNullOrEmpty(propertyName) || string.IsNullOrEmpty(searchTerm))
return source;
var property = typeof(T).GetProperty(propertyName);
if (property is null)
return source;
searchTerm = "%" + searchTerm + "%";
var itemParameter = Parameter(typeof(T), "item");
var functions = Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var like = typeof(DbFunctionsExtensions).GetMethod(nameof(DbFunctionsExtensions.Like), new Type[] functions.Type, typeof(string), typeof(string) );
Expression expressionProperty = Property(itemParameter, property.Name);
if (property.PropertyType != typeof(string))
expressionProperty = Call(expressionProperty, typeof(object).GetMethod(nameof(object.ToString), new Type[0]));
var selector = Call(
null,
like,
functions,
expressionProperty,
Constant(searchTerm));
return source.Where(Lambda<Func<T, bool>>(selector, itemParameter));
然后像这样使用它:
var query = _context.Set<Customer>().Search("FirstName", "Test").ToList();
var query2 = _context.Set<Customer>().Search("Age", "2").ToList();
作为参考,这是我使用的Customer
:
public class Customer
[Key]
public Guid Id get; set;
public string FirstName get; set;
public int Age get; set;
【讨论】:
非常感谢!那是我所希望的——已经有了分页的扩展方法。尚未阅读表达式树以完全理解您的代码。您是否正在使用扩展包中的某些类?Parameter
、Property
、Call
、Constant
和 Lambda
给我一些问题。
我只是在使用静态使用,像这样:using static System.Linq.Expressions.Expression;
【参考方案2】:
简单的回答,不。
EntityFramework 试图将您的 where 子句转换为 SQL 查询。此对话中没有对反射的原生支持。
这里有 2 个选项。您可以在查询之外构建文本或直接使用属性本身。是否有任何具体原因不使用以下内容?
var query = _context.Set<Customer>().Where(c => EF.Functions.Like(c.FirstName, "%Test%"));
【讨论】:
我尝试为提供的查询参数(dict - k:Properties,v:searchString)创建一个搜索要素类,在需要搜索功能的地方注入。最后,我有一个函数public IQueryable<T> ApplySearch<T>(IQueryable<T> efQuery) where T : class
,它在遍历查询参数时,使用附加的where 子句扩展查询:efQuery = efQuery.Where(t => EF.Functions.Like(t.GetType().GetProperty(queryParam.Key).GetValue(t).ToString(), "%" + queryParam.Value + "%"));
。这是主要目标。
使用反射几乎与EntityFramework相反。通常,当您有强类型查询时,您需要使用 ORM 工具。在您的情况下,您正在尝试动态构建一些东西。您仍然可以使用 EntityFramework 构建它,但它可能需要一些讨厌的代码。取而代之的是,您可以考虑使用 EF 通过反射逻辑执行构造查询?【参考方案3】:
请记住,您放在 Where 子句中的每个表达式树都必须转换为 SQL 查询。
正因为如此,你能写的ExpressionTrees非常有限,你必须遵守一些规则,这就是为什么不支持反射。
图片,而不是:
var query = _context.Set<Customer>().Where(c => EF.Functions.Like(c.GetType().GetProperty("FirstName").GetValue(c, null).ToString(), "%Test%"));
你写这样的东西:
var query = _context.Set<Customer>().Where(c => EF.Functions.Like(SomeMethodThatReturnsString(c), "%Test%"));
这意味着 EF 能够将任何 c# 代码转换为 SQL 查询 - 这显然不是真的 :)
【讨论】:
很棒的评论,谢谢!没有想通。必须跟进实际可翻译的内容。波兹卓亚姆! ;)【参考方案4】:我为那些使用 NpgSQL 作为他们的 EF Core 提供程序的人提供了一个已接受答案的版本,因为如果你想要不区分大小写,你将需要使用 ILike
函数,还添加了第二个版本,它结合了一堆属性到单个 Where()
子句中:
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, string propertyName, string searchTerm)
// Check property name
if (string.IsNullOrEmpty(propertyName))
throw new ArgumentNullException(nameof(propertyName));
// Check the search term
if(string.IsNullOrEmpty(searchTerm))
throw new ArgumentNullException(nameof(searchTerm));
// Check the property exists
var property = typeof(T).GetProperty(propertyName);
if (property == null)
throw new ArgumentException($"The property typeof(T).propertyName was not found.", nameof(propertyName));
// Check the property type
if(property.PropertyType != typeof(string))
throw new ArgumentException($"The specified property must be of type typeof(string).", nameof(propertyName));
// Get expression constants
var searchPattern = "%" + searchTerm + "%";
var itemParameter = Expression.Parameter(typeof(T), "item");
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] functions.Type, typeof(string), typeof(string) );
// Build the property expression and return it
Expression selectorExpression = Expression.Property(itemParameter, property.Name);
selectorExpression = Expression.Call(null, likeFunction, functions, selectorExpression, Expression.Constant(searchPattern));
return source.Where(Expression.Lambda<Func<T, bool>>(selectorExpression, itemParameter));
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, IEnumerable<string> propertyNames, string searchTerm)
// Check property name
if (!(propertyNames?.Any() ?? false))
throw new ArgumentNullException(nameof(propertyNames));
// Check the search term
if (string.IsNullOrEmpty(searchTerm))
throw new ArgumentNullException(nameof(searchTerm));
// Check the property exists
var properties = propertyNames.Select(p => typeof(T).GetProperty(p)).AsEnumerable();
if (properties.Any(p => p == null))
throw new ArgumentException($"One or more specified properties was not found on type typeof(T): string.Join(",", properties.Where(p => p == null).Select((p, i) => propertyNames.ElementAt(i))).", nameof(propertyNames));
// Check the property type
if (properties.Any(p => p.PropertyType != typeof(string)))
throw new ArgumentException($"The specified properties must be of type typeof(string): string.Join(",", properties.Where(p => p.PropertyType != typeof(string)).Select(p => p.Name)).", nameof(propertyNames));
// Get the expression constants
var searchPattern = "%" + searchTerm + "%";
var itemParameter = Expression.Parameter(typeof(T), "item");
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] functions.Type, typeof(string), typeof(string) );
// Build the expression and return it
Expression selectorExpression = null;
foreach (var property in properties)
var previousSelectorExpression = selectorExpression;
selectorExpression = Expression.Property(itemParameter, property.Name);
selectorExpression = Expression.Call(null, likeFunction, functions, selectorExpression, Expression.Constant(searchPattern));
if(previousSelectorExpression != null)
selectorExpression = Expression.Or(previousSelectorExpression, selectorExpression);
return source.Where(Expression.Lambda<Func<T, bool>>(selectorExpression, itemParameter));
【讨论】:
以上是关于Linq WHERE EF.Functions.Like - 为啥直接属性起作用而反射不起作用?的主要内容,如果未能解决你的问题,请参考以下文章