Linq WHERE EF.Functions.Like-为什么直接属性起作用而反射不起作用?

时间:2019-10-10 10:25:22

标签: c# linq reflection .net-core entity-framework-core

我尝试在数据库站点上执行简单的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:)

4 个答案:

答案 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));
    }