LINQ表达式中不区分大小写的字符串比较

时间:2013-07-02 14:34:30

标签: c# linq lambda expression-trees expressionvisitor

我正在尝试编写一个ExpressionVisitor来包装我的LINQ-to-object表达式,以自动使它们的字符串比较不区分大小写,就像它们在LINQ-to-entities中一样。

编辑:我确实想要使用ExpressionVisitor,而不是仅仅在创建它时为我的表达式应用一些自定义扩展或其他一些重要原因:传递给我的ExpressionVisitor的表达式由ASP.Net Web API ODATA生成层,所以我无法控制它是如何生成的(即我不能小写它正在搜索的字符串,除非在这个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);
    }

如果我在VisitMember方法的“返回表达式”行设置断点,然后对“节点”和“表达式”变量执行“ToString”,则断点会被击中两次,这就是两个集合值是:

首先点击:

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 个答案:

答案 0 :(得分: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);

    }
}

答案 1 :(得分: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