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.ValueLIKE 子句中使用的模式。 【参考方案1】:

在 EF 中,lambdasExpressionTrees,表达式被转换为 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; 

【讨论】:

非常感谢!那是我所希望的——已经有了分页的扩展方法。尚未阅读表达式树以完全理解您的代码。您是否正在使用扩展包中的某些类? ParameterPropertyCallConstantLambda 给我一些问题。 我只是在使用静态使用,像这样: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&lt;T&gt; ApplySearch&lt;T&gt;(IQueryable&lt;T&gt; efQuery) where T : class,它在遍历查询参数时,使用附加的where 子句扩展查询:efQuery = efQuery.Where(t =&gt; 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 - 为啥直接属性起作用而反射不起作用?的主要内容,如果未能解决你的问题,请参考以下文章

创建 Where 查询的 LINQ 扩展方法

Linq:Select 和 Where 有啥区别

linq 到实体,where 子句中的 where ? (内凡)

Linq 中的 Groupby 和 where 子句

概括 linq 查询中的 where 子句

Linq 之 Where操作