实现 "IndexOf" 忽略大小写的 C# 动态 lambda 问题

C# Dynamic lambda problem with implementing "IndexOf" ignore case

我一直在关注 pashov.net,尤其是他通过构建动态 Linq 表达式进行过滤的方法。

我实现了它并且有效,但是字符串搜索区分大小写。目前它没有 IndexOf StringComparison.OrdinalIgnoreCase 选项,所以我尝试添加一个。

一旦它到达它试图 运行 对其进行 lambda 调用的那部分代码,我就得到了一个错误... return Expression.Lambda<Func<T, bool>>(exp, param);

从 int32 转换为 bool 时遇到问题。

    System.ArgumentException
  HResult=0x80070057
  Message=Expression of type 'System.Int32' cannot be used for return type 'System.Boolean'
  Source=System.Linq.Expressions
  StackTrace:
   at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters, String paramName)
   at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
   at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, Boolean tailCall, IEnumerable`1 parameters)
   at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, ParameterExpression[] parameters)
   at JobsLedger.API.ControllerServices.Shared.OrderAndFIlterHelpers.DynamicFilteringHelper.ConstructAndExpressionTree[T](List`1 filters) in C:\AURELIA.0 - JobsLedgerSPA -ASPNET CORE 3.0\JobsLedger.API\ControllerServices\Shared\OrderAndFIlterHelpers\DynamicFilteringHelper.cs:line 48
   at JobsLedger.API.ControllerServices.Shared.ODataFilterAndSort.ParginatedFilteredSorted[T](IQueryable`1 source, String query) in C:\AURELIA.0 - JobsLedgerSPA -ASPNET CORE 3.0\JobsLedger.API\ControllerServices\Shared\OrderAndFIlterHelpers\ODataFilterAndSort.cs:line 41
   at JobsLedger.API.ControllerServices.API.App.ClientServices.ClientServices.GetPaginatedClients(String query) in C:\AURELIA.0 - JobsLedgerSPA -ASPNET CORE 3.0\JobsLedger.API\ControllerServices\API\App\ClientServices\ClientServices.cs:line 65
   at JobsLedger.API.Controllers.API.App.ClientController.Index(String query) in C:\AURELIA.0 - JobsLedgerSPA -ASPNET CORE 3.0\JobsLedger.API\Controllers\API\App\ClientController.cs:line 28
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

我做了一些 research,有人建议使用 .Convert 进行转换,但这没有用,因为没有从 int32 到 bool 的转换。

这是代码。

        public static Expression<Func<T, bool>> ConstructAndExpressionTree<T>(List<ExpressionFilter> filters) {
            if (filters.Count == 0)
                return null;

            ParameterExpression param = Expression.Parameter(typeof(T), "t");
            Expression exp = null;

            if (filters.Count == 1) {
                exp = ExpressionRetriever.GetExpression<T>(param, filters[0]);
            }
            else {
                exp = ExpressionRetriever.GetExpression<T>(param, filters[0]);
                for (int i = 1; i < filters.Count; i++) {
                    exp = Expression.And(exp, ExpressionRetriever.GetExpression<T>(param, filters[i]));
                }
            }

            return Expression.Lambda<Func<T, bool>>(exp, param); //.. FAILS HERE
        }

    public static class ExpressionRetriever {
        private static MethodInfo containsMethod = typeof(string).GetMethod("Contains");
        private static MethodInfo startsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
        private static MethodInfo endsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) });

        public static Expression GetExpression<T>(ParameterExpression param, ExpressionFilter filter) {
            MemberExpression member = Expression.Property(param, filter.PropertyName);
            ConstantExpression constant = Expression.Constant(filter.Value);
            switch (filter.Comparison) {
                case Comparison.Equal:
                    return Expression.Equal(member, constant);
                case Comparison.GreaterThan:
                    return Expression.GreaterThan(member, constant);
                case Comparison.GreaterThanOrEqual:
                    return Expression.GreaterThanOrEqual(member, constant);
                case Comparison.LessThan:
                    return Expression.LessThan(member, constant);
                case Comparison.LessThanOrEqual:
                    return Expression.LessThanOrEqual(member, constant);
                case Comparison.NotEqual:
                    return Expression.NotEqual(member, constant);
                case Comparison.Contains:
                    return Expression.Call(member, containsMethod, constant);
                case Comparison.StartsWith:
                    return Expression.Call(member, startsWithMethod, constant); 
                case Comparison.IndexOf:
                    var test = Expression.Call(member, "IndexOf", null, Expression.Constant(filter.Value, typeof(string)), Expression.Constant(StringComparison.InvariantCultureIgnoreCase));           
                    return test;
                    //return Expression.Convert(test, typeof(bool));
                case Comparison.EndsWith:
                    return Expression.Call(member, endsWithMethod, constant);
                default:
                    return null;
            }
        }
    }

如果我可以放入一些不需要像我所做的那样创建调用的代码,那就太好了。有没有办法根据其他字符串选项将其实现为方法等,或者考虑到我需要一个额外的参数,这是我尝试的唯一方法..

即如何实现 IndexOf 选项和 return 布尔值?

在 .NET Core 中有一个 Contains 的重载,它允许指定比较规则。在那里你可能会得到类似的东西:

public static class ExpressionRetriever
{
    private static MethodInfo containsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string), typeof(StringComparison)});
    ...

    public static Expression GetExpression<T>(ParameterExpression param, ExpressionFilter filter)
    {
        ...
        ConstantExpression comparisonType = Expression.Constant(StringComparison.OrdinalIgnoreCase);
        switch (filter.Comparison)
        {
            ...
            case Comparison.Contains:
                return Expression.Call(member, containsMethod, constant), comparisonType);
        }
    }
}

在任何其他情况下,您可以创建一个在表达式中使用的自定义比较方法,例如:

public class StringExtensions
{
    public static bool ContainsIgnoringCase(string str, string substring)
    {
        return str.IndexOf(substring, StringComparison.OrdinalIgnoreCase) >= 0;
    }
}

...

public static class ExpressionRetriever
{
    ...
    private static MethodInfo containsIgnoringCaseMethod = typeof(StringExtensions).GetMethod("ContainsIgnoringCase", new Type[] { typeof(string), typeof(string)});

    public static Expression GetExpression<T>(ParameterExpression param, ExpressionFilter filter)
    {
        ...
        switch (filter.Comparison)
        {
            ...
            case Comparison.Contains:
                return Expression.Call(null, containsIgnoringCaseMethod, member, constant);
        }
    }
}

来自 IndexOf 的文档,方法 returns:

The index position of the value parameter if that string is found, or -1 if it is not.

因此,为了检查 Foo.PropertyName 是否包含 Value,您需要创建以下表达式:

Foo.PropertyName.IndexOf(Value, StringComparison.InvariantCultureIgnoreCase) != -1

这意味着将您目前拥有的所有内容包装在 Expression.NotEqual(left, right):

case Comparison.IndexOf:
    return Expression.NotEqual(
        Expression.Call(
            member,
            "IndexOf",
            null,
            Expression.Constant(filter.Value, typeof(string)),
            Expression.Constant(StringComparison.InvariantCultureIgnoreCase, typeof(StringComparison))
        ),
        Expression.Constant(-1, typeof(int))
    );