You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor自定义DataTable组件内存动态过滤时Dynamic Expression不支持Like报错

Blazor DataTable组件内存数据Like过滤异常问题

我正在为Blazor开发一款DataTable组件,整体功能运行正常,但在使用Like对内存数据做动态过滤时出现问题,相关代码可查看项目对应源码文件。

public IList<TModel> Items { get; set; } = new List<TModel>();
private IQueryable<TModel> AllItems { get; set; }

if (AllFilterRules.Count == 0) Items = AllItems;
else
{
    Expression<Func<TModel, bool>>? filterExpression = null;

    foreach (var filterRule in AllFilterRules)
    {
        var filterRuleExpression = filterRule.GenerateExpression();

        if (filterExpression == null) filterExpression = filterRuleExpression;
        else filterExpression = PredicateBuilder.And(filterExpression, filterRuleExpression);
    }

    Items = AllItems.Where(filterExpression).ToList();

    useFilteredResult = true;
}

报错信息

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
渲染组件时出现未处理异常:'Like'方法不受支持,因为查询已切换到客户端计算。该问题通常出现在方法参数无法被翻译为服务端可执行逻辑的场景下,请重写查询避免参数的客户端计算,使方法可被翻译为服务端逻辑。
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.DataTable1[[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 560 at PSC.Blazor.Components.DataTable.DataTable1.d__172[[PSC.Blazor.Examples.Data.WeatherForecast, PSC.Blazor.Examples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext() in C:\Projects\PSC.Blazor.Components.DataTable\PSC.Blazor.Components.DataTable\DataTable.razor:line 511
at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)

过滤表达式实现代码

创建过滤表达式的函数如下:

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

我认为该函数本身逻辑正常,问题出在将过滤器应用到AllItems时:

Items = AllItems.Where(filterExpression).ToList();

我也尝试过将该行代码修改为遍历赋值的写法,仍然得到相同报错:

Items = new List<TModel>();
var t = AllItems.Where(filterExpression);
foreach (var i in t)
    Items.Add(i);

更新

以下是我通过快速监视查看filterExpression变量的结果:
快速监视结果

我还实现了Equals过滤的函数,运行完全正常:

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

第二次更新

正如开发者Richard指出的,EF Core 3.1版本中DbFunctionExtensions.Like方法可以支持内存查询,但后续版本的代码更新后,该方法在内存查询场景下会直接抛出InvalidOperationException,相关修改可查看EF Core官方仓库的对应提交记录。基于Expression<Func<TModel, bool>>的通用实现对除string外的所有类型都生效。

因此我为string类型单独添加了自定义实现:

public override IList<TModel> ApplyEmbeddedFilter<TModel>(IList<TModel> models, 
    string propertyName, string value)
{
    var searchStrLower = value.ToLower();
    var propsToCheck = typeof(TModel).GetProperties()
          .Where(a => a.PropertyType == typeof(string) && 
                 a.Name == propertyName && a.CanRead);

    return models.Where(obj => {
        foreach (PropertyInfo prop in propsToCheck)
        {
            string value = (string)prop.GetValue(obj);
            if (value != null && value.ToLower().Contains(searchStrLower)) 
                return true;
        }
        return false;
    }).ToList();
}

内容的提问来源于stack exchange,提问作者Enrico

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 23:36:04