我正在嘗試編寫一個ExpressionVisitor來包裝我的LINQ到對象表達式,以自動使它們的字符串比較不區分大小寫,就像它們在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()"
我不太了解表達式,以弄清楚我在這一點上做錯了什麼。有任何想法嗎?
我從你的代碼中製作了一個示例應用程序,看起來很有效:
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);
}
}
你可以像這樣創建一個extesion方法:
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