2017-10-14 20 views
1

J'ai un arbre d'expression provenant de Linq, par ex. leCollection.Where(...).OrderBy(...).Skip(n).Take(m). L'expression ressemble à:Ajout de `MethodCallExpression` au-dessus de` Expression` fournie

Take(Skip(OrderBy(Where(...), ...), n), m) // you got the idea 

Maintenant, c'est mon état idéal que j'ai là Take et Skip, mais ce n'est pas la règle. Je voudrais ajouter Take/Skip par programme si nécessaire.

je suis venu avec la manière comment changer Take argument/Skip, et je suis même en mesure d'ajouter Skip sous Take si je perçois ce n'est pas présent, mais je suis mal à comprendre comment ajouter Take en haut d'expression - Je ne sais pas comment reconnaître que je suis en train de visiter la meilleure expression. Les méthodes que j'ai écrites sont exécutées sur chaque appel de méthode dans l'arbre, donc j'ai dû vérifier le nom de la méthode avant de faire quoi que ce soit avec l'expression.

Voici les méthodes que je utilise pour modifier Take/Skip et en ajoutant Skip sous Take. Ceux qui travaillent, je suis également intéressé à placer Take sur l'arbre s'il n'est pas encore présent. Quelqu'un pourrait-il me diriger vers n'importe quel endroit de la sagesse, où je peux apprendre plus?

public class LeVisitor<TEntity> : ExpressionVisitor 
    where TEntity : class 
{ 
    private readonly int? _take; 
    private readonly int? _skip; 
    private readonly MethodInfo _queryableSkip; 

    public LeVisitor(int? take, int? skip) 
    { 
     // ... 
    } 

    protected override Expression VisitMethodCall(MethodCallExpression node) 
    { 
     return base.VisitMethodCall(AlterTake(AlterSkip(node))); 
    } 

    private MethodCallExpression AlterTake(MethodCallExpression node) 
    { 
     if (!_take.HasValue || !node.Method.Name.Equals("Take", StringComparison.Ordinal)) 
     { 
      return node; 
     } 

     Expression innerCall = node.Arguments[0]; 
     if (_skip != null) 
     { 
      var innerMethod = innerCall as MethodCallExpression; 
      if (innerMethod != null && !innerMethod.Method.Name.Equals("Skip", StringComparison.Ordinal)) 
      { 
       ConstantExpression skipConstant = Expression.Constant(_skip, typeof(int)); 
       innerCall = Expression.Call(_queryableSkip, new[] { innerCall, skipConstant }); 
      } 
     } 

     return node.Update(
      node.Object, 
      new[] 
      { 
       innerCall, 
       Expression.Constant(_take, typeof(int)) 
      }); 
    } 

    private MethodCallExpression AlterSkip(MethodCallExpression node) 
    { 
     if (!_skip.HasValue || !node.Method.Name.Equals("Skip", StringComparison.Ordinal)) 
     { 
      return node; 
     } 

     return node.Update(
      node.Object, 
      new[] 
      { 
       node.Arguments[0], 
       Expression.Constant(_skip, typeof(int)) 
      }); 
    } 
} 

Répondre

1

Vous pouvez remplacer Visit méthode et utiliser la variable de drapeau pour vérifier si cela est un premier appel à elle.
Code suivant vérifiera une méthode top et si ce n'est pas un Queryable.Take appel Take d'ajouter à

public class AddTakeVisitor : ExpressionVisitor 
{ 
    private readonly int takeAmount; 
    private bool firstEntry = true; 

    public AddTakeVisitor(int takeAmount) 
    { 
     this.takeAmount = takeAmount; 
    } 

    public override Expression Visit(Expression node) 
    { 
     if (!firstEntry) 
      return base.Visit(node); 

     firstEntry = false; 
     var methodCallExpression = node as MethodCallExpression; 
     if (methodCallExpression == null) 
      return base.Visit(node); 

     if (methodCallExpression.Method.Name == "Take") 
      return base.Visit(node); 

     var elementType = node.Type.GetGenericArguments(); 
     var methodInfo = typeof(Queryable) 
      .GetMethod("Take", BindingFlags.Public | BindingFlags.Static) 
      .MakeGenericMethod(elementType.First()); 
     return Expression.Call(methodInfo, node, Expression.Constant(takeAmount)); 
    } 
} 

Je l'ai testé avec ce code:

var exp = (new[] {1, 2, 3}).AsQueryable().Skip(1); 
var visitor = new AddTakeVisitor(1); 
var modified = visitor.Visit(exp.Expression); 

modified.DebugView ressemble à ceci:

.Call System.Linq.Queryable.Take(
    .Call System.Linq.Queryable.Skip(
     .Constant<System.Linq.EnumerableQuery`1[System.Int32]>(System.Int32[]), 
     1), 
    1)