我尝试在数据库站点上执行简单的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.Function不支持反射吗?
对不起,如果这些问题看起来很基本,那是我第一次使用.NET Core:)
答案 0 :(得分:2)
在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; }
}
答案 1 :(得分:1)
请记住,您在Where子句中放置的每个ExpresionTree必须转换为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查询-这显然不是真的:)
答案 2 :(得分:1)
简单答案,不。
EntityFramework试图将您的where子句隐藏到SQL查询中。在此对话中,没有对反射的本地支持。
您在这里有2个选项。您可以在查询之外构造文本,也可以直接使用属性本身。有没有特定原因不使用以下内容?
var query = _context.Set<Customer>().Where(c => EF.Functions.Like(c.FirstName, "%Test%"));
答案 3 :(得分:1)
对于使用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));
}