Sto cercando di scrivere un ExpressionVisitor per avvolgere le mie espressioni LINQ-to-object per rendere automaticamente i loro confronti tra stringhe maiuscole e minuscole, così come lo sarebbero in LINQ-to-entities.
EDIT: VOGLIO SICURAMENTE utilizzare un ExpressionVisitor anziché applicare semplicemente un'estensione personalizzata o qualcosa alla mia espressione quando viene creata per un motivo importante: l'espressione passata a ExpressionVisitor viene generata dal livello ODATA API Web ASP.Net, quindi Non ho il controllo su come viene generato (cioè non riesco a scrivere in minuscolo la stringa che sta cercando eccetto all'interno di questo ExpressionVisitor).
Deve supportare LINQ alle entità. Non solo estensione.
Ecco cosa ho finora. Cerca una chiamata a "Contains" su una stringa e quindi chiama ToLower su qualsiasi accesso membro all'interno di quella espressione.
Tuttavia, non funziona. Se visualizzo le espressioni dopo le mie modifiche, mi sembra corretto, quindi non sono sicuro di cosa potrei fare male.
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);
}
Se imposto un breakpoint sulla riga "return expression" nel metodo VisitMember e poi eseguo un "ToString" sulle variabili "node" e "expression", il punto di interruzione viene colpito due volte, ed ecco cosa sono i due set di valori :
Primo colpo:
node.ToString()
"$it.LastName"
expression.ToString()
"$it.LastName.ToLower()"
Secondo colpo:
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()"
Non ne so abbastanza delle espressioni per capire cosa sto facendo male a questo punto. Qualche idea?
Ho creato un'app campione dal tuo codice e sembra funzionante:
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);
}
}
puoi creare un metodo di estensione come questo:
public static class Extensions
{
public static bool InsensitiveEqual(this string val1, string val2)
{
return val1.Equals(val2, StringComparison.OrdinalIgnoreCase);
}
}
E quindi puoi chiamare così:
string teste = "teste";
string teste2 = "TESTE";
bool NOTREAL = teste.Equals(teste2); //FALSE
bool REAL = teste.InsensitiveEqual(teste2); //true