2013-07-02 14 views
6

Sto cercando di scrivere un ExpressionVisitor per avvolgere le mie espressioni LINQ su oggetto per rendere automaticamente i loro confronti tra maiuscole e minuscole senza distinzione tra maiuscole e minuscole, proprio come sarebbero in LINQ-to-entity.stringa senza confronti tra maiuscole e minuscole confronta nell'espressione LINQ

EDIT: VOGLIO SICURAMENTE utilizzare un ExpressionVisitor anziché applicare semplicemente un'estensione personalizzata o qualcosa alla mia espressione quando viene creato per un motivo importante: l'espressione passata al mio ExpressionVisitor viene generata dall'API Web ASP.Net Livello ODATA, quindi non ho il controllo sul modo in cui 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 quello che 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 ho impostato un punto di interruzione sulla linea di "espressione tornare" nel metodo VisitMember e poi fare un "ToString" sul "nodo" e variabili "espressione", il punto di rottura viene colpito due volte, e qui sta ciò che i due insiemi di valori sono:

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?

+1

'string.Equals (stringa1, stringa2, StringComparison.InvariantCultureIgnoreCase)'? – Corak

+1

Evita 'ToLower' per il confronto tra stringhe in quanto è più probabile che si verifichi un errore ([Test Turchia] (http://www.moserware.com/2008/02/does-your-code-pass-turkey-test.html)). O utilizzare maiuscole o preferibilmente, come suggerito da Corak, String.Equals. – keyboardP

+0

Questo non funzionerà nel mio caso. Innanzitutto, non ho il controllo su Expression, poiché viene generato automaticamente dall'API Web ASP.Net. In secondo luogo, voglio qualcosa che posso usare genericamente per concludere un'istruzione LINQ e che funzionerà sia con LINQ-to-entity che con LINQ-to-objects. –

risposta

2

ho fatto un'applicazione di esempio dal codice e sembra lavorare:

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

    } 
} 
+0

Hmm .... potrebbe avere qualcosa a che fare con l'espressione veramente lunga passata in contiene (annotata alla fine della mia domanda): "valore (System.Web.Http.OData.Query.Expressions.LinqParameterContainer + TypedLinqParameterContainer'1 [System.String]). TypedProperty". Non so di cosa si tratta, ma continuo ad applicare ToLower, poiché è un tipo String. –

+0

Oppure potrebbe avere qualcosa a che fare con il fatto che LINQ to Entities sta eseguendo l'espressione? Se provo a compilare/invocare direttamente l'espressione, ricevo un messaggio su "Questo metodo supporta l'infrastruttura INQ to Entities e non è destinato ad essere utilizzato direttamente dal codice. –

+0

Spiacente, non ho familiarità con OData. – Ben

0

è possibile creare un metodo extesion come questo:

public static class Extensions 
{ 
    public static bool InsensitiveEqual(this string val1, string val2) 
    { 
     return val1.Equals(val2, StringComparison.OrdinalIgnoreCase); 
    } 
} 

E poi si può chiamare in questo modo:

string teste = "teste"; 
string teste2 = "TESTE"; 

bool NOTREAL = teste.Equals(teste2); //FALSE 
bool REAL = teste.InsensitiveEqual(teste2); //true 
+0

Grazie, ma non funzionerà. Voglio utilizzare un ExpressionVisitor in modo da poter integrare la mia istruzione LINQ in modo tale che possa essere utilizzata sia in LINQ-to-objects che in LINQ-to-Entities. Un'estensione personalizzata InsensitiveEqual() non funzionerà in LINQ-to-Entities. –

+0

Inoltre, non ho il controllo sulla creazione dell'espressione, dal momento che il livello Odata API Web ASP.Net sta generando l'espressione. –