2016-01-27 1 views
8

Compte tenu d'une Expression<Func<TEntity, bool>> le long des lignes deExtrait toutes les conditions d'expression par type

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

Je suis en train d'extraire une des conditions de propriété de liste par type, par exemple

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" } ] 

Jusqu'à présent, j'ai créé un ExpressionVisitor et identifié la méthode VisitBinary comme celle que je veux brancher afin d'obtenir mes informations.

Je suis toujours à une perte sur

  • comment déterminer si le BinaryExpression je regarde représente une déclaration terminal (dans le sens où il y a des expressions plus imbriquées que je dois regarder)
  • comment déterminer le type d'entité que le BinaryExpression est concerné
  • si j'ai besoin de remplacer l'une des autres méthodes ExpressionVisitor pour couvrir les cas que je n'ai pas encore considérés.
+0

Pouvez-vous poster le code 'ExpressionVisitor'? – Shlomo

Répondre

4

Je ne sais pas ce qui est vraiment le cas d'utilisation, mais voici un point de départ

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'idée est simple. Remplacez la méthode Visit simplement pour conserver une pile d'expressions de traitement. Le traitement principal est à l'intérieur du remplacement VisitMember, qui est appelé pour chaque propriété/accesseur de champ. Le node.Expression.NodeType != ExpressionType.Constant est utilisé pour éliminer les éléments de fermeture, tandis que la seconde condition élimine les propriétés de collection. Enfin, l'expression de condition potentielle est extraite de la pile. Le résultat inclut à la fois MemberExpression et Expression où il est utilisé. MemberExpression.Expression.Type est le type de votre entité, MemberExpression.Member est la propriété/le domaine de ce type.

test de l'échantillon:

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

c'est une idée très intéressante !!! Malheureusement, il y a beaucoup trop de cas d'angle, que vous ne découvrirez que sur le chemin, comme des bugs. par exemple t.BoolProp == true && t.BoolProp signifient la même chose, mais sont représentés différemment. Qu'en est-il de t.CollectionProp.Any(), t.CollectionProp.FirstOrDefault()! = Null, etc etc etc .... Ce problème est très vite compilé! et je suis tout à fait sûr que dans certains cas, il n'est même pas clair comment procéder. Mais +1 pour la pile – MBoros