将EF Core中的“Like”函数表达式变为动态表达式

8

我已经编写了一些代码,用于创建动态表达式来过滤我的分页数据。

我正在尝试构建一个EF Core内置函数的动态表达式进行搜索(EF.Functions.Like)。

我尝试了下面这种方法,但它是一个扩展方法,并且在调用该方法时第一个参数未被使用。我不知道如何按照这种方式继续 ==> Ef => Function => Like。
该方法应该像这样使用 => Ef.Functions.Like("要搜索的属性", "%某个模式")

var likeMethod = typeof(DbFunctionsExtensions)
                        .GetMethods()
                        .Where(p => p.Name == "Like")
                        .First();
string pattern = $"%{finalConstant}%"; 

ConstantExpression likeConstant = Expression.Constant(pattern,typeof(string));

// the member expression is the property expression for example p.Name
var likeMethodCall = Expression.Call(method: likeMethod, arguments: new[] { memberExpression, likeConstant });

var searchLambda = Expression.Lambda<Func<T, bool>>(likeMethodCall, parameter);
query = query.Where(searchLambda);

但它抛出异常,显示:

为方法'Boolean Like(Microsoft.EntityFrameworkCore.DbFunctions, System.String, System.String)'提供的参数数量不正确\r\n参数名称:method


3
由于它是一个扩展方法,当您通过反射调用它时,需要提供所有参数:DbFunctions实例和2个字符串值。在调用中,只有2个参数。 - Zysce
3
.NET Core Npgsql.EntityFrameworkCore ILikeExpression 相同,但使用 DbFunctionsExtensionsLike - Ivan Stoev
3个回答

6
我基于这篇文章.NET Core Npgsql.EntityFrameworkCore ILikeExpression 实现了一个动态搜索。我做的是这样的:
我实现了[Searchable]属性,用它来标记需要执行搜索的属性。这些属性仅为字符串类型,如果需要,我可以解释如何搜索长整型和整型属性。
[AttributeUsage(AttributeTargets.Property)]
public class SearchableAttribute : Attribute
{
}

已经为IQueryable创建了一个扩展,它从搜索中接收输入字符串,并根据指定属性实现Like函数。
public static class QueryableExtension
{
    public static IQueryable<TEntityDto> ExecuteQueryFilter<TEntityDto>(this IQueryable<TEntityDto> queryable, string query)
        where TEntityDto : class, IEntityDto
    {
        // If the incoming request is empty, skip the search
        if (string.IsNullOrEmpty(query))
        {
            return queryable;
        }

        // We get all properties with type of string marked with our attribute
        var properties = typeof(TEntityDto).GetProperties()
            .Where(p => p.PropertyType == typeof(string) &&
                        p.GetCustomAttributes(typeof(SearchableAttribute), true).FirstOrDefault() != null)
            .Select(x => x.Name).ToList();

        // If there are no such properties, skip the search
        if (!properties.Any())
        {
            return queryable;
        }

        // Get our generic object
        ParameterExpression entity = Expression.Parameter(typeof(TEntityDto), "entity");

        // Get the Like Method from EF.Functions
        var efLikeMethod = typeof(DbFunctionsExtensions).GetMethod("Like",
            BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
            null,
            new[] { typeof(DbFunctions), typeof(string), typeof(string) },
            null);

        // We make a pattern for the search
        var pattern = Expression.Constant($"%{query}%", typeof(string));

        // Here we will collect a single search request for all properties
        Expression body = Expression.Constant(false);

        foreach (var propertyName in properties)
        {
            // Get property from our object
            var property = Expression.Property(entity, propertyName);

            // Сall the method with all the required arguments
            Expression expr = Expression.Call(efLikeMethod,
                    Expression.Property(null, typeof(EF), nameof(EF.Functions)), property, pattern);

            // Add to the main request
            body = Expression.OrElse(body, expr);
        }

        // Compose and pass the expression to Where
        var expression = Expression.Lambda<Func<TEntityDto, bool>>(body, entity);
        return queryable.Where(expression);
    }
}

Dto对象本身看起来像这样:

public class CategoryDto : IEntityDto
{
    public long Id { get; set; }

    [Searchable]
    public string Name { get; set; }

    [Searchable]
    public string IconKey { get; set; }

    public long UploadId { get; private set; }

    [Searchable]
    public string UploadFileName { get; set; }

    [Searchable]
    public string CreatedBy { get; set; }
    public DateTime Created { get; set; }
}

我在100万条记录中测试了这种搜索方法,对象名称由1到5个单词组成。搜索过程非常快。此处的性能优势在于表达式被转换为数据库端的LINQ to SQL。


2

这是一个可行的示例。

public static Expression<Func<T, bool>> Like<T>(Expression<Func<T, string>> prop, string keyword)
{
    var concatMethod = typeof(string).GetMethod(nameof(string.Concat), new[] { typeof(string), typeof(string) });
    return Expression.Lambda<Func<T, bool>>(
        Expression.Call(
            typeof(DbFunctionsExtensions),
            nameof(DbFunctionsExtensions.Like),
            null,
            Expression.Constant(EF.Functions),
            prop.Body,
            Expression.Add(
                Expression.Add(
                    Expression.Constant("%"),
                    Expression.Constant(keyword),
                    concatMethod),
                Expression.Constant("%"),
                concatMethod)),
        prop.Parameters);
}

query = query.Where(Like<User>(u => u.UserName, "angel"));

0

正如评论中所提到的,您需要将EF.Functions作为第一个参数包含在内:

var likeMethodCall = Expression.Call(likeMethod, new []
{
    Expression.Property(null, typeof(EF).GetProperty("Functions")),
    memberExpression,
    likeConstant 
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接