2016-01-27 54 views
8

Dato un Expression<Func<TEntity, bool>> lungo le linee diestratto tutte le condizioni di Espressione per tipo

entity => entity.SubEntity.Any(
    subEntity => (
     (subEntity.SomeProperty == False) 
     AndAlso 
     subEntity.SubSubEntity.FooProperty.StartsWith(
      value(SomeClass+<>c__DisplayClass0).ComparisonProperty 
     ) 
     AndAlso 
     subEntity.SubSubEntity.BarProperty == "Bar" 
     AndAlso 
     subEntity.SubSubEntity.SubSubSubEntity.Any(
      subSubSubEntity => (x.SubSubSubSubEntity.BazProperty == "whatever") 
     ) 
    ) 
) 

Sto cercando di estrarre un condizioni di proprietà elenco per tipo, vale a dire

TEntity    : [ /* no conditions for immediate members of TEntity */ ] 
TSubEntity   : [ { SomeProperty == False } ] 
TSubSubEntity  : [ { FooProperty.StartsWith(/* ... */) }, 
         { BarProperty == "Bar" } ], 
TSubSubSubEntity : [ /* no conditions for immediate members of TSubSubSubEntity */ ], 
TSubSubSubSubEntity : [ { BazProperty == "whatever" } ] 

Finora, ho creato un ExpressionVisitor e identificato il metodo VisitBinary come quello che voglio collegare per ottenere le mie informazioni.

Sono ancora in perdita su

  • come determinare se il BinaryExpression sto guardando rappresenta una dichiarazione terminale (nel senso che non ci sono le espressioni più nidificate che ho bisogno di guardare)
  • come determinare il tipo di Entità che il BinaryExpression riguarda
  • se è necessario sostituire uno degli altri metodi ExpressionVisitor per coprire casi che non ho ancora considerato.
+0

puoi pubblicare il codice 'ExpressionVisitor'? – Shlomo

risposta

4

Non so che cosa è davvero il caso d'uso, ma qui è un certo punto di partenza

class TestVisitor : ExpressionVisitor 
{ 
    public Dictionary<Type, List<Tuple<MemberExpression, Expression>>> Result = new Dictionary<Type, List<Tuple<MemberExpression, Expression>>>(); 
    Stack<Expression> stack = new Stack<Expression>(); 
    public override Expression Visit(Expression node) 
    { 
     stack.Push(node); 
     base.Visit(node); 
     stack.Pop(); 
     return node; 
    } 
    protected override Expression VisitMember(MemberExpression node) 
    { 
     if (node.Expression.NodeType != ExpressionType.Constant && (node.Type == typeof(string) || !typeof(IEnumerable).IsAssignableFrom(node.Type))) 
     { 
      var expression = stack.Skip(1).FirstOrDefault(); 
      if (expression != null && expression.Type == typeof(bool)) 
      { 
       List<Tuple<MemberExpression, Expression>> resultList; 
       if (!Result.TryGetValue(node.Expression.Type, out resultList)) 
        Result.Add(node.Expression.Type, resultList = new List<Tuple<MemberExpression, Expression>>()); 
       resultList.Add(Tuple.Create(node, expression)); 
      } 
     } 
     return base.VisitMember(node); 
    } 
} 

L'idea è semplice. Sostituire il metodo Visit solo per mantenere una pila di espressioni di elaborazione. L'elaborazione principale si trova all'interno della sovrascrittura VisitMember, che viene richiamata per ogni proprietà/campo di accesso. Il node.Expression.NodeType != ExpressionType.Constant viene utilizzato per eliminare i membri di chiusura, mentre la seconda condizione elimina le proprietà di raccolta. Infine, la potenziale espressione di condizione viene estratta dallo stack.

Il risultato include sia MemberExpression sia Expression dove viene utilizzato. MemberExpression.Expression.Type è il tipo di entità, MemberExpression.Member è la proprietà/campo di quel tipo.

prova del campione:

class Entity 
{ 
    public ICollection<SubEntity> SubEntity { get; set; } 
} 

class SubEntity 
{ 
    public bool SomeProperty { get; set; } 
    public SubSubEntity SubSubEntity { get; set; } 
} 

class SubSubEntity 
{ 
    public string FooProperty { get; set; } 
    public string BarProperty { get; set; } 
    public ICollection<SubSubSubEntity> SubSubSubEntity { get; set; } 
} 

class SubSubSubEntity 
{ 
    public SubSubSubSubEntity SubSubSubSubEntity { get; set; } 
} 

class SubSubSubSubEntity 
{ 
    public string BazProperty { get; set; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     string comparisonProperty = "Ivan"; 
     Expression<Func<Entity, bool>> e = 
      entity => entity.SubEntity.Any(subEntity => 
       subEntity.SomeProperty == false 
       && 
       subEntity.SubSubEntity.FooProperty.StartsWith(comparisonProperty) 
       && 
       subEntity.SubSubEntity.BarProperty == "Bar" 
       && 
       subEntity.SubSubEntity.SubSubSubEntity.Any(subSubSubEntity => subSubSubEntity.SubSubSubSubEntity.BazProperty == "whatever") 
       ); 
     var v = new TestVisitor(); 
     v.Visit(e); 
     var result = v.Result; 
    } 
} 
+2

questa è un'idea molto interessante !!! sfortunatamente ci sono troppi casi d'angolo, che scoprirai solo sulla strada, come bug. per esempio t.BoolProp == true && t.BoolProp significano lo stesso, ma sono rappresentati in modo abbastanza diverso. Che ne dite di t.CollectionProp.Any(), t.CollectionProp.FirstOrDefault()! = Null, etc etc etc .... Questo problema è molto presto compilato !!! e sono abbastanza sicuro che in alcuni casi non è ancora chiaro come procedere. Ma +1 per lo stack – MBoros