C # Динамическая проблема лямбда с реализацией IndexOf игнорировать регистр

Я слежу за pashov.net и, в частности, за его подходом к фильтрации путем создания динамического Linq. выражения.

Я реализовал его, и он работал, но поиск строки чувствителен к регистру. В настоящее время у него нет параметра IndexOf StringComparison.OrdinalIgnoreCase, поэтому я попытался добавить его.

Я получаю сообщение об ошибке, как только он достигает той части кода, где он пытается запустить на нем лямбда-вызов ... 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\1.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\1.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\1.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\1.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()

Я провел несколько исследований, и мне было предложено преобразовать его с помощью .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 и вернуть bool?


person si2030    schedule 07.09.2019    source источник
comment
Просто хочу убедиться, что вы знаете, какое выражение вам на самом деле нужно построить - stackoverflow.com/ questions / 444798 / перед тем, как углубиться в вопрос ...   -  person Alexei Levenkov    schedule 07.09.2019
comment
Да, IndexOf возвращает число (то есть индекс), а не логическое значение. Я не уверен, когда вы ожидаете, что это вернет true, а когда - false, но я думаю, что вы, возможно, пытаетесь повторно реализовать Contains под другим именем   -  person Andrew Williamson    schedule 07.09.2019
comment
На самом деле я не очень беспокоюсь о том, содержит ли он индекс и т. Д. Просто нужно, чтобы это было нечувствительным к регистру для поиска .. хотя оцените ответ.   -  person si2030    schedule 07.09.2019
comment
Просто посмотрел на этот вопрос, Алексей, и да, его нечувствительность к регистру. Я после ... просто не знаю, как реализовать это с текущим кодом и динамической лямбдой .. Думаю, мне может потребоваться добавить еще один параметр выражения ..   -  person si2030    schedule 07.09.2019


Ответы (2)


В .NET Core есть перегрузка Содержит, который позволяет указать правила сравнения. Там вы можете получить что-то вроде:

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);
        }
    }
}
person user7217806    schedule 07.09.2019
comment
Единственная проблема с использованием подхода пользовательского сравнения заключается в том, что он предназначен для преобразования в SQL. - person Nkosi; 08.09.2019

Из документации для IndexOf метод возвращает:

Позиция индекса параметра значения, если эта строка найдена, или -1, если нет.

Итак, чтобы проверить, содержит ли 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))
    );
person Andrew Williamson    schedule 07.09.2019