Quantcast
Channel: Development With A Dot
Viewing all articles
Browse latest Browse all 404

Intercepting LINQ Queries

$
0
0

A common request when working with LINQ queries (Entity Framework, NHibernate, etc) is the ability to intercept them, that is, inspect an existing query and possibly modify something in it. This is not extremely difficult to do “by hand”, but Microsoft has a nice class called ExpressionVisitor which makes the job easier. It basically has virtual methods that get called whenever the class visits each expression contained in a greater expression, which may come from a query (the IQueryable interface exposes the underlying Expression in its Expression property). The virtual methods even allow returning a replacement for each expression found, the only problem is that you must subclass ExpressionVisitor to make even the slightest change, so I wrote my own class, which exposes all node traversal as events, one event for each kind of expression, where you can return an alternative expression, thus changing the original query. Here is the code for it:

   1:publicsealedclass ExpressionInterceptor : ExpressionVisitor
   2: {
   3:#region Public events
   4:publicevent Func<BinaryExpression, BinaryExpression> Binary;
   5:publicevent Func<BlockExpression, BlockExpression> Block;
   6:publicevent Func<CatchBlock, CatchBlock> CatchBlock;
   7:publicevent Func<ConditionalExpression, ConditionalExpression> Conditional;
   8:publicevent Func<ConstantExpression, ConstantExpression> Constant;
   9:publicevent Func<DebugInfoExpression, DebugInfoExpression> DebugInfo;
  10:publicevent Func<DefaultExpression, DefaultExpression> Default;
  11:publicevent Func<DynamicExpression, DynamicExpression> Dynamic;
  12:publicevent Func<ElementInit, ElementInit> ElementInit;
  13:publicevent Func<Expression, Expression> Expression;
  14:publicevent Func<Expression, Expression> Extension;
  15:publicevent Func<GotoExpression, GotoExpression> Goto;
  16:publicevent Func<IndexExpression, IndexExpression> Index;
  17:publicevent Func<InvocationExpression, InvocationExpression> Invocation;
  18:publicevent Func<LabelExpression, LabelExpression> Label;
  19:publicevent Func<LabelTarget, LabelTarget> LabelTarget;
  20:publicevent Func<LambdaExpression, LambdaExpression> Lambda;
  21:publicevent Func<ListInitExpression, ListInitExpression> ListInit;
  22:publicevent Func<LoopExpression, LoopExpression> Loop;
  23:publicevent Func<MemberExpression, MemberExpression> Member;
  24:publicevent Func<MemberAssignment, MemberAssignment> MemberAssignment;
  25:publicevent Func<MethodCallExpression, MethodCallExpression> MethodCall;
  26:publicevent Func<MemberInitExpression, MemberInitExpression> MemberInit;
  27:publicevent Func<NewExpression, NewExpression> New;
  28:publicevent Func<NewArrayExpression, NewArrayExpression> NewArray;
  29:publicevent Func<ParameterExpression, ParameterExpression> Parameter;
  30:publicevent Func<RuntimeVariablesExpression, RuntimeVariablesExpression> RuntimeVariables;
  31:publicevent Func<SwitchExpression, SwitchExpression> Switch;
  32:publicevent Func<TryExpression, TryExpression> Try;
  33:publicevent Func<TypeBinaryExpression, TypeBinaryExpression> TypeBinary;
  34:publicevent Func<UnaryExpression, UnaryExpression> Unary;
  35:#endregion
  36:  
  37:#region Public methods
  38:public IQueryable<T> Visit<T>(IQueryable<T> query)
  39:     {
  40:return (this.Visit(query as IQueryable) as IQueryable<T>);
  41:     }
  42:  
  43:public IQueryable<T> Visit<T, TExpression>(IQueryable<T> query, Func<TExpression, TExpression> action) where TExpression : Expression
  44:     {
  45:         EventInfo evt = this.GetType().GetEvents(BindingFlags.Public | BindingFlags.Instance).Where(x => x.EventHandlerType == typeof(Func<TExpression, TExpression>)).First();
  46:         evt.AddEventHandler(this, action);
  47:  
  48:         query = this.Visit(query);
  49:  
  50:         evt.RemoveEventHandler(this, action);
  51:  
  52:return (query);
  53:     }
  54:  
  55:public IQueryable Visit(IQueryable query)
  56:     {
  57:return (query.Provider.CreateQuery(this.Visit(query.Expression)));
  58:     }
  59:  
  60:public IEnumerable<Expression> Flatten(IQueryable query)
  61:     {
  62:         Queue<Expression> list = new Queue<Expression>();
  63:         Func<Expression, Expression> action = delegate(Expression expression)
  64:         {
  65:if (expression != null)
  66:             {
  67:                 list.Enqueue(expression);
  68:             }
  69:  
  70:return (expression);
  71:         };
  72:  
  73:this.Expression += action;
  74:  
  75:this.Visit(query);
  76:  
  77:this.Expression -= action;
  78:  
  79:return (list);
  80:     }
  81:#endregion
  82:  
  83:#region Public override methods
  84:publicoverride Expression Visit(Expression node)
  85:     {
  86:if ((this.Expression != null) && (node != null))
  87:         {
  88:return(base.Visit(this.Expression(base.Visit(node))));
  89:         }
  90:else
  91:         {
  92:return (base.Visit(node));
  93:         }
  94:     }
  95:#endregion
  96:  
  97:#region Protected override methods
  98:protectedoverride Expression VisitNew(NewExpression node)
  99:     {
 100:if ((this.New != null) && (node != null))
 101:         {
 102:return (base.VisitNew(this.New(node)));
 103:         }
 104:else
 105:         {
 106:return (base.VisitNew(node));
 107:         }
 108:     }
 109:  
 110:protectedoverride Expression VisitNewArray(NewArrayExpression node)
 111:     {
 112:if ((this.NewArray != null) && (node != null))
 113:         {
 114:return (base.VisitNewArray(this.NewArray(node)));
 115:         }
 116:else
 117:         {
 118:return (base.VisitNewArray(node));
 119:         }
 120:     }
 121:  
 122:protectedoverride Expression VisitParameter(ParameterExpression node)
 123:     {
 124:if ((this.Parameter != null) && (node != null))
 125:         {
 126:return (base.VisitParameter(this.Parameter(node)));
 127:         }
 128:else
 129:         {
 130:return (base.VisitParameter(node));
 131:         }
 132:     }
 133:  
 134:protectedoverride Expression VisitRuntimeVariables(RuntimeVariablesExpression node)
 135:     {
 136:if ((this.RuntimeVariables != null) && (node != null))
 137:         {
 138:return (base.VisitRuntimeVariables(this.RuntimeVariables(node)));
 139:         }
 140:else
 141:         {
 142:return (base.VisitRuntimeVariables(node));
 143:         }
 144:     }
 145:  
 146:protectedoverride Expression VisitSwitch(SwitchExpression node)
 147:     {
 148:if ((this.Switch != null) && (node != null))
 149:         {
 150:return (base.VisitSwitch(this.Switch(node)));
 151:         }
 152:else
 153:         {
 154:return (base.VisitSwitch(node));
 155:         }
 156:     }
 157:  
 158:protectedoverride Expression VisitTry(TryExpression node)
 159:     {
 160:if ((this.Try != null) && (node != null))
 161:         {
 162:return (base.VisitTry(this.Try(node)));
 163:         }
 164:else
 165:         {
 166:return (base.VisitTry(node));
 167:         }
 168:     }
 169:  
 170:protectedoverride Expression VisitTypeBinary(TypeBinaryExpression node)
 171:     {
 172:if ((this.TypeBinary != null) && (node != null))
 173:         {
 174:return (base.VisitTypeBinary(this.TypeBinary(node)));
 175:         }
 176:else
 177:         {
 178:return (base.VisitTypeBinary(node));
 179:         }
 180:     }
 181:  
 182:protectedoverride Expression VisitUnary(UnaryExpression node)
 183:     {
 184:if ((this.Unary != null) && (node != null))
 185:         {
 186:return (base.VisitUnary(this.Unary(node)));
 187:         }
 188:else
 189:         {
 190:return (base.VisitUnary(node));
 191:         }
 192:     }
 193:  
 194:protectedoverride Expression VisitMemberInit(MemberInitExpression node)
 195:     {
 196:if ((this.MemberInit != null) && (node != null))
 197:         {
 198:return (base.VisitMemberInit(this.MemberInit(node)));
 199:         }
 200:else
 201:         {
 202:return (base.VisitMemberInit(node));
 203:         }
 204:     }
 205:  
 206:protectedoverride Expression VisitMethodCall(MethodCallExpression node)
 207:     {
 208:if ((this.MethodCall != null) && (node != null))
 209:         {
 210:return (base.VisitMethodCall(this.MethodCall(node)));
 211:         }
 212:else
 213:         {
 214:return (base.VisitMethodCall(node));
 215:         }
 216:     }
 217:  
 218:
 219:protectedoverride Expression VisitLambda<T>(Expression<T> node)
 220:     {
 221:if ((this.Lambda != null) && (node != null))
 222:         {
 223:return (base.VisitLambda<T>(this.Lambda(node) as Expression<T>));
 224:         }
 225:else
 226:         {
 227:return (base.VisitLambda<T>(node));
 228:         }
 229:     }
 230:  
 231:protectedoverride Expression VisitBinary(BinaryExpression node)
 232:     {
 233:if ((this.Binary != null) && (node != null))
 234:         {
 235:return (base.VisitBinary(this.Binary(node)));
 236:         }
 237:else
 238:         {
 239:return (base.VisitBinary(node));
 240:         }
 241:     }
 242:  
 243:protectedoverride Expression VisitBlock(BlockExpression node)
 244:     {
 245:if ((this.Block != null) && (node != null))
 246:         {
 247:return (base.VisitBlock(this.Block(node)));
 248:         }
 249:else
 250:         {
 251:return (base.VisitBlock(node));
 252:         }
 253:     }
 254:  
 255:protectedoverride CatchBlock VisitCatchBlock(CatchBlock node)
 256:     {
 257:if ((this.CatchBlock != null) && (node != null))
 258:         {
 259:return (base.VisitCatchBlock(this.CatchBlock(node)));
 260:         }
 261:else
 262:         {
 263:return (base.VisitCatchBlock(node));
 264:         }
 265:     }
 266:  
 267:protectedoverride Expression VisitConditional(ConditionalExpression node)
 268:     {
 269:if ((this.Conditional != null) && (node != null))
 270:         {
 271:return (base.VisitConditional(this.Conditional(node)));
 272:         }
 273:else
 274:         {
 275:return (base.VisitConditional(node));
 276:         }
 277:     }
 278:  
 279:protectedoverride Expression VisitConstant(ConstantExpression node)
 280:     {
 281:if ((this.Constant != null) && (node != null))
 282:         {
 283:return (base.VisitConstant(this.Constant(node)));
 284:         }
 285:else
 286:         {
 287:return (base.VisitConstant(node));
 288:         }
 289:     }
 290:  
 291:protectedoverride Expression VisitDebugInfo(DebugInfoExpression node)
 292:     {
 293:if ((this.DebugInfo != null) && (node != null))
 294:         {
 295:return (base.VisitDebugInfo(this.DebugInfo(node)));
 296:         }
 297:else
 298:         {
 299:return (base.VisitDebugInfo(node));
 300:         }
 301:     }
 302:  
 303:protectedoverride Expression VisitDefault(DefaultExpression node)
 304:     {
 305:if ((this.Default != null) && (node != null))
 306:         {
 307:return (base.VisitDefault(this.Default(node)));
 308:         }
 309:else
 310:         {
 311:return (base.VisitDefault(node));
 312:         }
 313:     }
 314:  
 315:protectedoverride Expression VisitDynamic(DynamicExpression node)
 316:     {
 317:if ((this.Dynamic != null) && (node != null))
 318:         {
 319:return (base.VisitDynamic(this.Dynamic(node)));
 320:         }
 321:else
 322:         {
 323:return (base.VisitDynamic(node));
 324:         }
 325:     }
 326:  
 327:protectedoverride ElementInit VisitElementInit(ElementInit node)
 328:     {
 329:if ((this.ElementInit != null) && (node != null))
 330:         {
 331:return (base.VisitElementInit(this.ElementInit(node)));
 332:         }
 333:else
 334:         {
 335:return (base.VisitElementInit(node));
 336:         }
 337:     }
 338:  
 339:protectedoverride Expression VisitExtension(Expression node)
 340:     {
 341:if ((this.Extension != null) && (node != null))
 342:         {
 343:return (base.VisitExtension(this.Extension(node)));
 344:         }
 345:else
 346:         {
 347:return (base.VisitExtension(node));
 348:         }
 349:     }
 350:  
 351:protectedoverride Expression VisitGoto(GotoExpression node)
 352:     {
 353:if ((this.Goto != null) && (node != null))
 354:         {
 355:return (base.VisitGoto(this.Goto(node)));
 356:         }
 357:else
 358:         {
 359:return (base.VisitGoto(node));
 360:         }
 361:     }
 362:  
 363:protectedoverride Expression VisitIndex(IndexExpression node)
 364:     {
 365:if ((this.Index != null) && (node != null))
 366:         {
 367:return (base.VisitIndex(this.Index(node)));
 368:         }
 369:else
 370:         {
 371:return (base.VisitIndex(node));
 372:         }
 373:     }
 374:  
 375:protectedoverride Expression VisitInvocation(InvocationExpression node)
 376:     {
 377:if ((this.Invocation != null) && (node != null))
 378:         {
 379:return (base.VisitInvocation(this.Invocation(node)));
 380:         }
 381:else
 382:         {
 383:return (base.VisitInvocation(node));
 384:         }
 385:     }
 386:  
 387:protectedoverride Expression VisitLabel(LabelExpression node)
 388:     {
 389:if ((this.Label != null) && (node != null))
 390:         {
 391:return (base.VisitLabel(this.Label(node)));
 392:         }
 393:else
 394:         {
 395:return (base.VisitLabel(node));
 396:         }
 397:     }
 398:  
 399:protectedoverride LabelTarget VisitLabelTarget(LabelTarget node)
 400:     {
 401:if ((this.LabelTarget != null) && (node != null))
 402:         {
 403:return (base.VisitLabelTarget(this.LabelTarget(node)));
 404:         }
 405:else
 406:         {
 407:return (base.VisitLabelTarget(node));
 408:         }
 409:     }
 410:  
 411:protectedoverride Expression VisitListInit(ListInitExpression node)
 412:     {
 413:if ((this.ListInit != null) && (node != null))
 414:         {
 415:return (base.VisitListInit(this.ListInit(node)));
 416:         }
 417:else
 418:         {
 419:return (base.VisitListInit(node));
 420:         }
 421:     }
 422:  
 423:protectedoverride Expression VisitLoop(LoopExpression node)
 424:     {
 425:if ((this.Loop != null) && (node != null))
 426:         {
 427:return (base.VisitLoop(this.Loop(node)));
 428:         }
 429:else
 430:         {
 431:return (base.VisitLoop(node));
 432:         }
 433:     }
 434:  
 435:protectedoverride Expression VisitMember(MemberExpression node)
 436:     {
 437:if ((this.Member != null) && (node != null))
 438:         {
 439:return (base.VisitMember(this.Member(node)));
 440:         }
 441:else
 442:         {
 443:return (base.VisitMember(node));
 444:         }
 445:     }
 446:  
 447:protectedoverride MemberAssignment VisitMemberAssignment(MemberAssignment node)
 448:     {
 449:if ((this.MemberAssignment != null) && (node != null))
 450:         {
 451:return (base.VisitMemberAssignment(this.MemberAssignment(node)));
 452:         }
 453:else
 454:         {
 455:return (base.VisitMemberAssignment(node));
 456:         }
 457:     }
 458:#endregion
 459: }

Yes, I know, I probably should have used properties instead of events, but that’s really not important.

A simple example might be:

   1: ExpressionInterceptor interceptor = new ExpressionInterceptor();
   2: String[] lettersArray = new String[] { "A", "B", "C" };    //a data source
   3: IQueryable<String> lettersQuery = lettersArray.AsQueryable().Where(x => x == "A").OrderByDescending(x => x).Select(x => x.ToUpper());    //a silly query
   4: IQueryable<String> lettersInterceptedQuery = interceptor.Visit<String, MethodCallExpression>(lettersQuery, x =>
   5: {
   6:if (x.Method.Name == "ToUpper")
   7:     {
   8://change from uppercase to lowercase
   9:         x = Expression.Call(x.Object, typeof(String).GetMethods().Where(y => y.Name == "ToLower").First());
  10:     }
  11:  
  12:return (x);
  13: });
  14: lettersInterceptedQuery = interceptor.Visit<String, BinaryExpression>(lettersInterceptedQuery, x =>
  15: {
  16://change from qual to not equal
  17:     x = Expression.MakeBinary(ExpressionType.NotEqual, x.Left, x.Right);
  18:  
  19:return (x);
  20: });
  21: IEnumerable<Expression> lettersExpressions = interceptor.Flatten(lettersQuery);    //all expressions found
  22: IEnumerable<String> lettersList = lettersQuery.ToList();    //"A"
  23: IEnumerable<String> lettersInterceptedList = lettersInterceptedQuery.ToList();    //"c", "b"

You see, I have methods that visit both an IQueryable, an IQueryable<T> or an Expression, and there is even an inline version that takes a Func<TExpression, TExpression> for even easier usage.

As always, hope you find it useful!


Viewing all articles
Browse latest Browse all 404

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>