2017-10-14 212 views
1

我有表达式树源于Linq,例如leCollection.Where(...).OrderBy(...).Skip(n).Take(m)。表达的样子:现在在提供的`Expression'之上添加`MethodCallExpression`

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

,这是我的理想状态,我有TakeSkip存在,但它是不是规则。如果需要,我想以编程方式添加Take/Skip

我想出办法如何改变Take/Skip的说法,我甚至可以添加SkipTake下,如果我发现这是不存在的,但我在努力弄清楚如何在顶部加入Take的表达 - 我不知道如何识别我实际上访问的顶级表达。我写的方法是在树中的每个方法调用中执行的,所以我必须在对表达式做任何事情之前检查方法名称。

这里是我用来改变Take/Skip和在Take下加Skip的方法。那些工作,我现在也有兴趣将Take放在树顶部,如果它还没有出现。任何人都可以指引我到任何智慧的地方,在那里我可以学到更多?

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

回答

1

您可以覆盖Visit方法和使用标志变量来检查,如果这是它一个非常第一个电话。
接下来代码将检查顶部方法,如果它不是一个Take插件调用Queryable.Take

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

我使用此代码测试它:

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

modified.DebugView看起来是这样的:

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