用 Entity Framework 和 Like 进行动态表达

Make dynamic expression with Entity Framework and Like

在我的项目中,我在 运行 时创建基于 Entity Framework Core 的表达式。该代码是 Blazor 组件的一部分。我有一些基于变量类型的函数。例如,我有这个功能,它正在工作,检查一个字段是否等于一个值

private class IsEqualsFilter : ObjectFilter
{
    public override bool ValueRequired => true;

    public override bool IsNumberAllowed => true;

    public override bool IsBoolAllowed => true;

    public override bool IsStringAllowed => true;

    public override bool IsDateTimeAllowed => true;

    public override bool IsNonNullableAllowed => true;

    internal IsEqualsFilter(int id, string name)
        : base(id, name)
    {
    }

    public override Expression<Func<TModel, bool>> GenerateExpression<TModel>(
        string propertyName,
        object value)
    {
        ParameterExpression parameterExpression = Expression.Parameter(typeof(TModel), "e");
        Expression expression = (Expression)parameterExpression;

        string str = propertyName;
        char[] chArray = new char[1] { '.' };

        foreach (string propertyOrFieldName in str.Split(chArray))
            expression = (Expression)Expression.PropertyOrField(expression, propertyOrFieldName);

        UnaryExpression unaryExpression = !expression.Type.IsEnum ?
                Expression.ConvertChecked(Expression.Constant(value), expression.Type) :
                Expression.ConvertChecked(Expression.Constant(
                (object)Convert.ToInt32(Enum.Parse(expression.Type, value.ToString()))), 
                expression.Type);

        return Expression.Lambda<Func<TModel, bool>>(Expression.Equal(expression, unaryExpression), 
            parameterExpression);
    }
}

现在,我有另一个函数来检查字段是否包含值,但是这个函数给我一个错误

private class ContainsFilter : ObjectFilter
{
    public override bool ValueRequired => true;

    public override bool IsNumberAllowed => false;

    public override bool IsBoolAllowed => false;

    public override bool IsStringAllowed => true;

    public override bool IsDateTimeAllowed => false;

    public override bool IsNonNullableAllowed => true;

    internal ContainsFilter(int id, string name)
        : base(id, name)
    {
    }

    public override Expression<Func<TModel, bool>> GenerateExpression<TModel>(
        string propertyName,
        object value)
    {
        Expression expression = (Expression)Expression.Parameter(typeof(TModel), "e");

        string str = propertyName;
        char[] chArray = new char[1] { '.' };

        foreach (string propertyOrFieldName in str.Split(chArray))
            expression = (Expression)Expression.PropertyOrField(expression, propertyOrFieldName);

        ConstantExpression constantExpression = Expression.Constant((object)string.Format("%{0}%", value));
            
        return Expression.Lambda<Func<TModel, bool>>(Expression.Call(typeof(DbFunctionsExtensions), 
            nameof(DbFunctionsExtensions.Like), null, Expression.Constant(EF.Functions), 
            expression, constantExpression));
    }
}

我得到的错误是

System.ArgumentException: Incorrect number of parameters supplied for lambda declaration

at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters, String paramName)

at System.Linq.Expressions.Expression.Lambda[Func2](Expression body, String name, Boolean tailCall, IEnumerable1 parameters)

at System.Linq.Expressions.Expression.Lambda[Func2](Expression body, Boolean tailCall, IEnumerable1 parameters)

at System.Linq.Expressions.Expression.Lambda[Func`2](Expression body, ParameterExpression[] parameters)

at PSC.Blazor.Components.DataTable.Code.Enumerations.ObjectFilter.ContainsFilter.GenerateExpression[WeatherForecast](String propertyName, Object value) in C:\Projects\PSC.Blazor.Components.DataTable\PSC.Blazor.Components.DataTable\Code\Enumerations\ObjectFilter.cs:line 347

我遵循函数nExpression.Lambda<Func<TModel, bool>>的签名。如何使用 Like 更改呼叫?

更新

我尝试了 Richard 建议的代码,但我遇到了这个错误。

Unhandled exception rendering component: The 'Like' method is not supported because the query has switched to client-evaluation. This usually happens when the arguments to the method cannot be translated to server. Rewrite the query to avoid client evaluation of arguments so that method can be translated to server. System.InvalidOperationException: The 'Like' method is not supported because the query has switched to client-evaluation. This usually happens when the arguments to the method cannot be translated to server. Rewrite the query to avoid client evaluation of arguments so that method can be translated to server. at Microsoft.EntityFrameworkCore.DbFunctionsExtensions.LikeCore(String matchExpression, String pattern, String escapeCharacter) at Microsoft.EntityFrameworkCore.DbFunctionsExtensions.Like(DbFunctions _, String matchExpression, String pattern) at System.Linq.Enumerable.WhereArrayIterator1[[PSC.Blazor.Examples.Data.WeatherForecast, PSC.Blazor.Examples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext() at System.Collections.Generic.List1[[PSC.Blazor.Examples.Data.WeatherForecast, PSC.Blazor.Examples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[WeatherForecast](IEnumerable1 source)
at PSC.Blazor.Components.DataTable.DataTable`1[[PSC.Blazor.Examples.Data.WeatherForecast, PSC.Blazor.Examples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].PerformClientSideDataManipulations() in C:\Projects\PSC.Blazor.Components.DataTable\PSC.Blazor.Components.DataTable\DataTable.razor:line 559

您忘记在 Expression.Lambda 调用中指定参数:

ParameterExpression parameterExpression = Expression.Parameter(typeof(TModel), "e");
Expression expression = (Expression)parameterExpression;

string str = propertyName;
char[] chArray = new char[1] { '.' };

foreach (string propertyOrFieldName in str.Split(chArray))
    expression = (Expression)Expression.PropertyOrField(expression, propertyOrFieldName);

ConstantExpression valueExpression = Expression.Constant((object)string.Format("%{0}%", value));

Expression body = Expression.Call(typeof(DbFunctionsExtensions), 
    nameof(DbFunctionsExtensions.Like), null, Expression.Constant(EF.Functions), 
    expression, valueExpression);

return Expression.Lambda<Func<TModel, bool>>(body, parameterExpression);

编辑: 我怀疑 Expression.Constant(EF.Functions) 无法翻译成 SQL。幸运的是,这不是必需的;请改用 Expression.Default(typeof(DbFunctions))

Expression body = Expression.Call(typeof(DbFunctionsExtensions), 
    nameof(DbFunctionsExtensions.Like), null, Expression.Default(typeof(DbFunctions)), 
    expression, valueExpression);

编辑 2: 尝试明确获取 MethodInfo

MethodInfo likeMethod = typeof(DbFunctionsExtensions).GetMethod(
    nameof(DbFunctionsExtensions.Like),
    BindingFlags.Public | BindingFlags.Static,
    null,
    new[] { typeof(DbFunctions), typeof(string), typeof(string) },
    null);

Expression body = Expression.Call(null, 
    likeMethod, Expression.Default(typeof(DbFunctions)), 
    expression, valueExpression);