Сравнение строк без учета регистра в выражении LINQ

Я пытаюсь написать ExpressionVisitor, чтобы обернуть вокруг моих выражений LINQ-to-object, чтобы автоматически сделать их сравнения строк нечувствительными к регистру, как это было бы в LINQ-to-entity.

РЕДАКТИРОВАТЬ: Я ОБЯЗАТЕЛЬНО хочу использовать ExpressionVisitor, а не просто применять какое-либо пользовательское расширение или что-то к моему выражению, когда оно создано по одной важной причине: выражение, передаваемое моему ExpressionVisitor, генерируется слоем ODATA ASP.Net Web API, поэтому У меня нет контроля над тем, как он генерируется (то есть я не могу прописать строку, которую он ищет, кроме как внутри этого ExpressionVisitor).

Должен поддерживать LINQ to Entities. Не просто расширение.

Вот что у меня так далеко. Он ищет вызов "Contains" в строке, а затем вызывает ToLower для любого доступа к члену внутри этого выражения.

Тем не менее, это не работает. Если я просматриваю выражения после своих изменений, мне они кажутся правильными, поэтому я не уверен, что могу делать неправильно.

public class CaseInsensitiveExpressionVisitor : ExpressionVisitor
{

    protected override Expression VisitMember(MemberExpression node)
    {
        if (insideContains)
        {
            if (node.Type == typeof (String))
            {
                var methodInfo = typeof (String).GetMethod("ToLower", new Type[] {});
                var expression = Expression.Call(node, methodInfo);
                return expression;
            }
        }
        return base.VisitMember(node);
    }

    private Boolean insideContains = false;
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.Name == "Contains")
        {
            if (insideContains) throw new NotSupportedException();
            insideContains = true;
            var result = base.VisitMethodCall(node);
            insideContains = false;
            return result;
        }
        return base.VisitMethodCall(node);
    }

Если я устанавливаю точку останова в строке "return expression" в методе VisitMember, а затем выполняю "ToString" для переменных "node" и "expression", точка прерывания получает двойное попадание, и вот каковы эти два набора значений:

Первый удар:

node.ToString()
"$it.LastName"
expression.ToString()
"$it.LastName.ToLower()"

Второй удар:

node.ToString()
"value(System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String]).TypedProperty"
expression.ToString()
"value(System.Web.Http.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String]).TypedProperty.ToLower()"

Я не знаю достаточно о выражениях, чтобы понять, что я делаю не так в данный момент. Есть идеи?

2 ответа

Решение

Я сделал пример приложения из вашего кода, и он работает:

    public class Test
{
    public string Name;
}
public class CaseInsensitiveExpressionVisitor : ExpressionVisitor
{

    protected override Expression VisitMember(MemberExpression node)
    {
        if (insideContains)
        {
            if (node.Type == typeof (String))
            {
                var methodInfo = typeof (String).GetMethod("ToLower", new Type[] {});
                var expression = Expression.Call(node, methodInfo);
                return expression;
            }
        }
        return base.VisitMember(node);
    }

    private Boolean insideContains = false;

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.Name == "Contains")
        {
            if (insideContains) throw new NotSupportedException();
            insideContains = true;
            var result = base.VisitMethodCall(node);
            insideContains = false;
            return result;
        }
        return base.VisitMethodCall(node);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Expression <Func<Test, bool>> expr = (t) => t.Name.Contains("a");
        var  expr1 = (Expression<Func<Test, bool>>) new CaseInsensitiveExpressionVisitor().Visit(expr);
        var test = new[] {new Test {Name = "A"}};
        var length = test.Where(expr1.Compile()).ToArray().Length;
        Debug.Assert(length == 1);
        Debug.Assert(test.Where(expr.Compile()).ToArray().Length == 0);

    }
}

Вы можете создать метод расширения следующим образом:

public static class Extensions
{
    public static bool InsensitiveEqual(this string val1, string val2)
    {
        return val1.Equals(val2, StringComparison.OrdinalIgnoreCase);
    }
}

И тогда вы можете позвонить так:

string teste = "teste";
string teste2 = "TESTE";

bool NOTREAL = teste.Equals(teste2); //FALSE
bool REAL = teste.InsensitiveEqual(teste2); //true
Другие вопросы по тегам