【问题标题】:Case insensitive string compare in LINQ expressionLINQ 表达式中不区分大小写的字符串比较
【发布时间】:2013-06-29 23:41:18
【问题描述】:

我正在尝试编写一个 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()"

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

【问题讨论】:

  • string.Equals(string1, string2, StringComparison.InvariantCultureIgnoreCase)?
  • 避免使用 ToLower 进行字符串比较,因为它更有可能导致错误 (Turkey Test)。要么使用大写,要么最好使用 Corak 建议的 String.Equals。
  • 这在我的情况下不起作用。首先,我无法控制表达式,因为它是由 ASP.Net Web API 自动生成的。其次,我想要一些我可以用来包装 LINQ 语句的东西,并且可以与 LINQ-to-entities 和 LINQ-to-objects 一起使用。
  • @keyboardP:是的,我读到了土耳其测试。在这一点上,我对此并不担心。但是,一旦我开始工作,我会尝试使用大写字母。
  • @JoshMouch,相信你做得很好,你最终是否手动编写了整个 ODATA --> LINQ --> SQL,没有 ODATA V4 框架没有帮助? stackoverflow.com/q/47055350/1431250

标签: c# linq lambda expression-trees expressionvisitor


【解决方案1】:

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

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

    }
}

【讨论】:

  • Hmm.... 是否与传递给包含的真正长表达式有关(在我的问题末尾注明):“value(System.Web.Http.OData.Query .Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String]).TypedProperty”。我不知道它是什么,但我仍然对它应用 ToLower,因为它是 String 类型。
  • 或者它是否与 LINQ to Entities 正在执行表达式这一事实有关?如果我尝试直接编译/调用表达式,我会收到一条消息:“此方法支持 INQ to Entities 基础结构,不打算直接从您的代码中使用)。
  • 对不起,我对 OData 不熟悉。
  • 您说您希望 linq2Objects 具有相同的行为。所以我假设你已经对对象进行了查询。如果出现错误“此方法支持..”通常意味着您丢失了 db 的上下文。例如,在上下文处理后调用表达式。
  • 其实你是对的。我的代码正在运行!问题是 IQueryable 在从 OData Get 传回之前被转换为 IList,然后又回到 IQueryable。第一个 IQueryable 应用了我的 ExpressionVisitor,但不是第二个。既然你让我思考,就把你的标记为答案。
【解决方案2】:

你可以像这样创建一个扩展方法:

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

【讨论】:

  • 谢谢,但这行不通。我想使用 ExpressionVisitor,这样我就可以包装我的 LINQ 语句,这样我就可以在 LINQ-to-objects 和 LINQ-to-Entities 中使用它。自定义扩展 InsensitiveEqual() 在 LINQ-to-Entities 中不起作用。
  • 另外,我无法控制表达式的创建,因为 ASP.Net Web API Odata 层正在生成表达式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-27
  • 2016-02-28
  • 2021-05-09
  • 2015-07-23
  • 2012-11-30
相关资源
最近更新 更多