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!