7

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

编辑:我绝对想使用 ExpressionVisitor 而不仅仅是在创建表达式时应用一些自定义扩展或其他东西,原因有一个:传递给我的 ExpressionVisitor 的表达式是由 ASP.Net Web API ODATA 层生成的,所以我无法控制它是如何生成的(即我不能小写它正在搜索的字符串,除非在这个 ExpressionVisitor 中)。

必须支持 LINQ to Entities。不仅仅是扩展。

这是我到目前为止所拥有的。它在字符串上查找对“包含”的调用,然后对该表达式内的任何成员访问调用 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()"

我对表达式的了解还不够,无法弄清楚我在这一点上做错了什么。有任何想法吗?

4

2 回答 2

3

我从您的代码中制作了一个示例应用程序,它似乎可以正常工作:

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

    }
}
于 2013-07-02T16:19:03.787 回答
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
于 2013-07-02T14:51:10.173 回答