【问题标题】:Dynamic Expression doesn't support Like动态表达式不支持 Like
【发布时间】:2021-11-12 11:47:57
【问题描述】:

我正在为 Blazor 创建一个 DataTable 组件。它正在工作,但是当我想使用Like 动态过滤内存中的数据时遇到问题(请参阅代码here

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

暴击:Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]

未处理的异常呈现组件:不支持“Like”方法,因为查询已切换到客户端评估。这通常发生在方法的参数无法转换到服务器时。重写查询以避免客户端评估参数,以便将方法转换为服务器。

System.InvalidOperationException:不支持“Like”方法,因为查询已切换到客户端评估。这通常发生在方法的参数无法转换到服务器时。重写查询以避免客户端评估参数,以便将方法转换为服务器。

在 Microsoft.EntityFrameworkCore.DbFunctionsExtensions.LikeCore(String matchExpression, String pattern, String escapeCharacter)

在 Microsoft.EntityFrameworkCore.DbFunctionsExtensions.Like(DbFunctions _, String matchExpression, String pattern)

在 System.Linq.Enumerable.WhereArrayIterator`1[[PSC.Blazor.Examples.Data.WeatherForecast, PSC.Blazor.Examples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext()

在 System.Collections.Generic.List1[[PSC.Blazor.Examples.Data.WeatherForecast, PSC.Blazor.Examples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]..ctor(IEnumerable1 集合)

在 System.Linq.Enumerable.ToList[WeatherForecast](IEnumerable`1 源)

在 PSC.Blazor.Components.DataTable.DataTable`1[[PSC.Blazor.Examples.Data.WeatherForecast, PSC.Blazor.Examples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].PerformClientSideDataManipulations () 在 C:\Projects\PSC.Blazor.Components.DataTable\PSC.Blazor.Components.DataTable\DataTable.razor:560 行

在 PSC.Blazor.Components.DataTable.DataTable`1.d__172[[PSC.Blazor.Examples.Data.WeatherForecast,PSC.Blazor.Examples,版本=1.0.0.0,文化=中性,PublicKeyToken=null]] C:\Projects\PSC.Blazor.Components.DataTable\PSC.Blazor.Components.DataTable\DataTable.razor 中的 .MoveNext():第 511 行

在 Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(任务任务)

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

更新/2

正如Richard 所说,在 EF Core 3.1 中,DbFunctionExtensions.Like 方法适用于内存查询;但此后代码已更新为仅在 commit 中引发 InvalidOperationException。 Expression&lt;Func&lt;TModel, bool&gt;&gt; 的通用实现适用于除 string 之外的各种 Type

所以,我为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();
}

【问题讨论】:

  • 异常信息怎么样,是没用还是不清楚?
  • 尝试检查 filterExpression 的 DebugView:它是否包含内联的 filterExpressionfilterRuleExpression 部分,或者它是否尝试调用另一个 lambda?
  • 这看起来像原始代码,而不是来自yesterday's question 的固定代码。
  • 谢谢大家的回答。 @RichardDeeming 没有。正如你所建议的,我将AllItems 更改为IQuerable,但我得到了同样的错误。
  • 令人讨厌的是,在 EF Core 3.1 中,DbFunctionExtensions.Like 方法适用于内存查询;但此后代码已更新为仅在 commit 47d1ebedf834f1b4caae4d4f45f5f54d788e31af 中抛出 InvalidOperationException

标签: c# linq expression


【解决方案1】:

错误本身与动态查询无关。你可以像这样复制它:

using Microsoft.EntityFrameworkCore;
// ...

IQueryable<string> test = (new[] { "a", "b" }).AsQueryable();
var result = test.Where(c => EF.Functions.Like(c, "a")).ToArray();

这将引发相同的异常,这是您使用ContainsFilter 中的表达式动态构建的一种查询。原因是 EF.Funcions.Like 不打算与内存中的集合一起使用。它的唯一目的是通过 EF Core 表达式树分析器进行分析并转换为 SQL LIKE 语句。如果你正常执行这个函数(当你将它与内存集合一起使用时会发生这种情况)它只会抛出异常:

EF.Functions.Like("a", "b"); // throws the same exception

所以要过滤内存,你需要使用不同的东西(也许只是基本的string.Contains 就可以,也许不会,我们不知道)。

【讨论】:

  • @canton7 OP 在第一行提到:“但是当我想用 Like 动态过滤 内存中的数据时遇到问题”。因此,他重用了用于数据库查询的过滤器,在内存中执行类似的操作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-31
  • 2020-11-30
  • 1970-01-01
  • 2017-06-07
  • 1970-01-01
  • 1970-01-01
  • 2014-08-11
相关资源
最近更新 更多