EFCore 中带有动态 where 子句的 LINQ

LINQ with dynamic where clause in EFCore

我正在使用 net5.0 和 EntityFrameworkCore 5.0.4。

我有一个搜索方法,该方法具有可选的字符串以在 EFCore 中的 DataContext 上搜索。

我想检查每个字符串是否不为空或不为空 space。

我能做到:

var query = context.Model.AsQueryable();

if (!string.IsNullOrWhiteSpace(parameters.Id))
{
  query = query.Where(x => x.Id.ToLower().Contains(parameters.Id.ToLower()));
}

但这很难维护。我开始尝试的是:

public static IQueryable<T> FilterBy<T>(this IQueryable<T> query, string? searchValue,
    Expression<Func<T, string>> getValueExpression)
{
    if (string.IsNullOrWhiteSpace(searchValue?.Trim()))
    {
        return query;
    }

    // return with a where clause
}

我可以使用它,但它不支持访问我需要的连接,我觉得这不是一个好方法:

var searchLower = searchValue.Trim().ToLower();
var propertyName = getValueExpression.GetMemberAccess().Name;
return query.Where(x => EF.Property<string?>(x!, propertyName)!.ToLower().Contains(searchLower));

我想这样使用它:

query                        
.FilterBy(parameters.Id, x => x.Id)
.FilterBy(parameters.Name, x => x.Name)
.FilterBy(parameters.CompanyName, x => x.Company.Name) // access via include/join

解决方案

    private static readonly MethodInfo StringContainsMethod =
        typeof(string).GetMethod(nameof(string.Contains), new[] {typeof(string)})!;
    private static readonly MethodInfo StringToLowerMethod =
        typeof(string).GetMethod(nameof(string.ToLower), Type.EmptyTypes)!;

    public static IQueryable<T> FilterBy<T>(this IQueryable<T> query, string? searchValue,
        Expression<Func<T, string?>> memberExpression)
    {
        if (string.IsNullOrWhiteSpace(searchValue))
            return query;

        var valueExpression = Expression.Constant(searchValue.ToLower());
        var toLower = Expression.Call(memberExpression.Body, StringToLowerMethod);
        var call = Expression.Call(toLower, StringContainsMethod, valueExpression);
        var sourceParam = memberExpression.Parameters.First();
        Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(call, sourceParam);

        return query.Where(predicate);
    }

整数

    public static IQueryable<T> FilterBy<T>(this IQueryable<T> query, int? filterInteger,
        Expression<Func<T, int?>> memberExpression)
    {
        if (filterInteger == null)
            return query;

        var valueExpression = Expression.Constant(filterInteger);
        var call = Expression.Equal(memberExpression.Body, valueExpression);
        var sourceParam = memberExpression.Parameters.First();
        Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(call, sourceParam);
        return query.Where(predicate);
    }

布尔值

    public static IQueryable<T> FilterBy<T>(this IQueryable<T> query, bool? filterBoolean,
        Expression<Func<T, bool?>> memberExpression)
    {
        if (filterBoolean == null)
            return query;

        var valueExpression = Expression.Constant(filterBoolean);
        var call = Expression.Equal(memberExpression.Body, valueExpression);
        var sourceParam = memberExpression.Parameters.First();
        Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(call, sourceParam);
        return query.Where(predicate);
    }

我没有对此进行测试,但我相信您需要使用表达式来实现这一点。以下将构建一个表达式,用作 Where 方法中的谓词:

    public static IQueryable<T> FilterBy<T>(
        this IQueryable<T> query, 
        string searchValue,
        Expression<Func<T, string>> memberExpression)
    {
        if (string.IsNullOrWhiteSpace(searchValue))
            return query;

        // must be a lambda expression
        LambdaExpression lambdaExpression = memberExpression as LambdaExpression;
        if (lambdaExpression == null)
            throw new ArgumentException($"Expression '{memberExpression}' is not a lambda expression.");

        // get the member
        Func<ParameterExpression, Expression> sourceExpression = source => Expression.Invoke(lambdaExpression, source);
        ParameterExpression sourceParameter = Expression.Parameter(typeof(T), "source");
        Expression sourceMember = sourceExpression(sourceParameter);

        // expression for the search value
        ConstantExpression valueExpression = Expression.Constant(searchValue);

        // expression to call the Contains method
        MethodInfo containsMethod = GetContainsMethod();
        MethodCallExpression callExpression = Expression.Call(null, containsMethod, sourceMember, valueExpression);

        // predicate expression
        Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(callExpression, sourceParameter);

        return query.Where(predicate);
    }

    private static MethodInfo GetContainsMethod()
    {
        // get method
        MethodInfo genericContainsMethod = typeof(Queryable)
            .GetMethods(BindingFlags.Static | BindingFlags.Public)
            .First(m => m.Name == "Contains"
                     && m.IsGenericMethod
                     && m.GetParameters().Count() == 2);

        // apply generic types
        MethodInfo containsMethod = genericContainsMethod
            .MakeGenericMethod(new Type[] { typeof(string) });

        return containsMethod;
    }