SHOW:
|
|
- or go back to the newest paste.
1 | /// <summary> | |
2 | - | /// v0.3 Codename: Playing with Fire |
2 | + | /// v0.4 Codename: Crossing without Looking |
3 | /// | |
4 | /// (previous version v0.1 Codename: Running with Scissors: http://pastebin.com/6qLs8TPt) | |
5 | /// (previous version v0.2 Codename: Lost in Space: lost in space :-) ) | |
6 | /// (previous version v0.3 Codename: Playing with Fire: http://pastebin.com/pRbKt1Z2) | |
7 | /// | |
8 | /// Support class with an extension method to make "Safe" an IQueryable | |
9 | /// or IQueryable<T>. Safe as "I work for another company, thousand | |
10 | /// of miles from you. I do hope I won't ever buy/need something from | |
11 | /// your company". | |
12 | /// The Extension methods wraps a IQueryable in a wrapper that then can | |
13 | /// be used to execute a query. If the original Provider doesn't suppport | |
14 | /// some methods, the query will be partially executed by the Provider | |
15 | /// and partially executed locally. | |
16 | /// | |
17 | /// Note that this **won't** play nice with the Object Tracking! | |
18 | /// | |
19 | /// Not suitable for programmers under 5 years (of experience)! | |
20 | /// Dangerous if inhaled or executed. | |
21 | /// </summary> | |
22 | public static class SafeQueryable | |
23 | { | |
24 | /// <summary> | |
25 | /// Optional logger to log the queries that are "corrected. Note that | |
26 | /// there is no "strong guarantee" that the IQueriable (that is also | |
27 | /// an IQueryProvider) is executing its (as in IQueriable.Expression) | |
28 | /// Expression, so an explicit Expression parameter is passed. This | |
29 | /// because the IQueryProvider.Execute method receives an explicit | |
30 | /// expression parameter. Clearly there is a "weak guarantee" that | |
31 | /// unless you do "strange things" this won't happen :-) | |
32 | /// </summary> | |
33 | public static Action<IQueryable, Expression, NotSupportedException> Logger { get; set; } | |
34 | ||
35 | /// <summary> | |
36 | /// Return a "Safe" IQueryable<T>. | |
37 | /// </summary> | |
38 | /// <typeparam name="T"></typeparam> | |
39 | /// <param name="source"></param> | |
40 | /// <returns></returns> | |
41 | public static IQueryable<T> AsSafe<T>(this IQueryable<T> source) | |
42 | { | |
43 | if (source is SafeQueryable<T>) | |
44 | { | |
45 | return source; | |
46 | } | |
47 | ||
48 | return new SafeQueryable<T>(source); | |
49 | } | |
50 | } | |
51 | ||
52 | /// <summary> | |
53 | /// Simple interface useful to collect the Exception, or to recognize | |
54 | /// a SafeQueryable<T>. | |
55 | /// </summary> | |
56 | public interface ISafeQueryable | |
57 | { | |
58 | NotSupportedException Exception { get; } | |
59 | } | |
60 | ||
61 | /// <summary> | |
62 | /// "Safe" wrapper around a IQueryable<T;> | |
63 | /// </summary> | |
64 | /// <typeparam name="T"></typeparam> | |
65 | public class SafeQueryable<T> : IOrderedQueryable<T>, IQueryProvider, ISafeQueryable | |
66 | { | |
67 | protected static readonly FieldInfo StackTraceStringField = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); | |
68 | public readonly IQueryable<T> Query; | |
69 | ||
70 | /// <summary> | |
71 | /// Logging of the "main" NotSupportedException. | |
72 | /// </summary> | |
73 | public NotSupportedException Exception { get; protected set; } | |
74 | ||
75 | public SafeQueryable(IQueryable<T> query) | |
76 | { | |
77 | Query = query; | |
78 | } | |
79 | ||
80 | /* IQueryable<T> */ | |
81 | ||
82 | public IEnumerator<T> GetEnumerator() | |
83 | { | |
84 | return Provider.Execute<IEnumerable<T>>(Expression).GetEnumerator(); | |
85 | } | |
86 | ||
87 | IEnumerator IEnumerable.GetEnumerator() | |
88 | { | |
89 | return GetEnumerator(); | |
90 | } | |
91 | ||
92 | public Type ElementType | |
93 | { | |
94 | get { return Query.ElementType; } | |
95 | } | |
96 | ||
97 | public Expression Expression | |
98 | { | |
99 | get { return Query.Expression; } | |
100 | } | |
101 | ||
102 | public IQueryProvider Provider | |
103 | { | |
104 | get { return this; } | |
105 | } | |
106 | ||
107 | /* IQueryProvider */ | |
108 | ||
109 | public IQueryable<TElement> CreateQuery<TElement>(Expression expression) | |
110 | { | |
111 | return CreateQueryImpl<TElement>(expression); | |
112 | } | |
113 | ||
114 | public IQueryable CreateQuery(Expression expression) | |
115 | { | |
116 | Type iqueryableArgument = GetIQueryableTypeArgument(expression.Type); | |
117 | MethodInfo createQueryImplMethod = typeof(SafeQueryable<T>) | |
118 | .GetMethod("CreateQueryImpl", BindingFlags.Instance | BindingFlags.NonPublic) | |
119 | .MakeGenericMethod(iqueryableArgument); | |
120 | ||
121 | return (IQueryable)createQueryImplMethod.Invoke(this, new[] { expression }); | |
122 | } | |
123 | ||
124 | public TResult Execute<TResult>(Expression expression) | |
125 | { | |
126 | return ExecuteImpl<TResult>(expression); | |
127 | } | |
128 | ||
129 | public object Execute(Expression expression) | |
130 | { | |
131 | Type iqueryableArgument = GetIQueryableTypeArgument(expression.Type); | |
132 | MethodInfo executeImplMethod = typeof(SafeQueryable<T>) | |
133 | .GetMethod("ExecuteImpl", BindingFlags.Instance | BindingFlags.NonPublic) | |
134 | .MakeGenericMethod(iqueryableArgument); | |
135 | ||
136 | return executeImplMethod.Invoke(this, new[] { expression }); | |
137 | } | |
138 | ||
139 | /* Implementation methods */ | |
140 | ||
141 | /// <summary> | |
142 | /// Gets the T of IQueryablelt;T> | |
143 | /// </summary> | |
144 | /// <param name="type"></param> | |
145 | /// <returns></returns> | |
146 | protected static Type GetIQueryableTypeArgument(Type type) | |
147 | { | |
148 | IEnumerable<Type> interfaces = type.IsInterface ? | |
149 | new[] { type }.Concat(type.GetInterfaces()) : | |
150 | type.GetInterfaces(); | |
151 | Type argument = (from x in interfaces | |
152 | where x.IsGenericType | |
153 | let gt = x.GetGenericTypeDefinition() | |
154 | where gt == typeof(IQueryable<>) | |
155 | select x.GetGenericArguments()[0]).FirstOrDefault(); | |
156 | return argument; | |
157 | } | |
158 | ||
159 | protected IQueryable<TElement> CreateQueryImpl<TElement>(Expression expression) | |
160 | { | |
161 | return new SafeQueryable<TElement>(Query.Provider.CreateQuery<TElement>(expression)); | |
162 | } | |
163 | ||
164 | protected TResult ExecuteImpl<TResult>(Expression expression) | |
165 | { | |
166 | Exception = null; | |
167 | ||
168 | try | |
169 | { | |
170 | // We try executing it directly | |
171 | TResult result = Query.Provider.Execute<TResult>(expression); | |
172 | ||
173 | // Success! | |
174 | return result; | |
175 | } | |
176 | catch (NotSupportedException e) | |
177 | { | |
178 | // Clearly there was a NotSupportedException :-) | |
179 | ||
180 | // We "augment" the exception with the full stack trace | |
181 | AugmentStackTrace(e); | |
182 | ||
183 | if (SafeQueryable.Logger != null) | |
184 | { | |
185 | SafeQueryable.Logger(this, expression, e); | |
186 | } | |
187 | ||
188 | // We save this first exception | |
189 | Exception = e; | |
190 | ||
191 | // Much easier to call it through reflection. In this way we | |
192 | // can pass him some generic types | |
193 | MethodInfo executeSplittedMethod = typeof(SafeQueryable<T>).GetMethod("ExecuteSplitted", BindingFlags.Static | BindingFlags.NonPublic); | |
194 | ||
195 | MethodCallExpression call; | |
196 | Expression innerExpression = expression; | |
197 | Type iqueryableArgument; | |
198 | ||
199 | // We want to check that there is a MethodCallExpression with | |
200 | // at least one argument, and that argument is an Expression | |
201 | // of type IQueryable<iqueryableArgument>, and we save the | |
202 | // iqueryableArgument | |
203 | while ((call = innerExpression as MethodCallExpression) != null && | |
204 | call.Arguments.Count > 0 && | |
205 | (innerExpression = call.Arguments[0] as Expression) != null && | |
206 | (iqueryableArgument = GetIQueryableTypeArgument(innerExpression.Type)) != null) | |
207 | { | |
208 | try | |
209 | { | |
210 | TResult result = (TResult)executeSplittedMethod.MakeGenericMethod(iqueryableArgument, typeof(TResult)).Invoke(null, new object[] { Query, expression, call, innerExpression }); | |
211 | ||
212 | // Success! | |
213 | return result; | |
214 | } | |
215 | catch (TargetInvocationException ex) | |
216 | { | |
217 | if (!(ex.InnerException is NotSupportedException)) | |
218 | { | |
219 | throw; | |
220 | } | |
221 | } | |
222 | } | |
223 | ||
224 | throw; | |
225 | } | |
226 | } | |
227 | ||
228 | protected static TResult ExecuteSplitted<TInner, TResult>(IQueryable queryable, Expression expression, MethodCallExpression call, Expression innerExpression) | |
229 | { | |
230 | // We do know that all the "inner" parts of the query return/use | |
231 | // IQueryable<> (we checked it when calling this method), | |
232 | // So we can execute them asking for a IEnumerable<>. | |
233 | // Only the most external part of the query (the First/Last/Min/ | |
234 | // Max/...) | |
235 | // can return a non-IQueryable<>, but that one has already been | |
236 | // handled and splitted | |
237 | // The NotSupportedException should happen here | |
238 | IEnumerable<TInner> enumerable = queryable.Provider.Execute<IEnumerable<TInner>>(innerExpression); | |
239 | ||
240 | // "Quick-n-dirty". We use a fake Queryable. The "right" way | |
241 | // would probably be transform all the outer query from | |
242 | // IQueryable.Method<T> to IEnumerable.Method<T> | |
243 | // Too much long, and it's probably what AsQueryable does :-) | |
244 | // Note that this Queryable is executed in "local" (the | |
245 | // AsQueryable<T> method is nearly useless... The second time in | |
246 | // my life I was able to use it for something) | |
247 | IQueryable<TInner> fakeQueryable = Queryable.AsQueryable(enumerable); | |
248 | ||
249 | // We rebuild a new expression by changing the "old" inner | |
250 | // parameter of the MethodCallExpression with the queryable we | |
251 | // just built | |
252 | Expression expressionWithFake = new SimpleExpressionReplacer | |
253 | { | |
254 | Call = call, | |
255 | Queryable = fakeQueryable | |
256 | }.Visit(expression); | |
257 | ||
258 | // We "execute" locally the whole query through a second "outer" | |
259 | // instance of the EnumerableQuery (this class is the class that | |
260 | // "implements" the "fake-magic" of AsQueryable) | |
261 | EnumerableQuery<TResult> fullQueryableWithFake = new EnumerableQuery<TResult>(expressionWithFake); | |
262 | TResult result = ((IQueryProvider)fullQueryableWithFake).Execute<TResult>(expressionWithFake); | |
263 | return result; | |
264 | } | |
265 | ||
266 | /// <summary> | |
267 | /// The StackTrace of an Exception "stops" at the catch. This method | |
268 | /// "augments" it to include the full stack trace. | |
269 | /// </summary> | |
270 | /// <param name="e"></param> | |
271 | /// <param name="skipFrames"></param> | |
272 | protected static void AugmentStackTrace(Exception e, int skipFrames = 2) | |
273 | { | |
274 | // Playing with a private field here. Don't do it at home :-) | |
275 | // If not present, do nothing. | |
276 | if (StackTraceStringField == null) | |
277 | { | |
278 | return; | |
279 | } | |
280 | ||
281 | string stack1 = e.StackTrace; | |
282 | string stack2 = new StackTrace(skipFrames, true).ToString(); | |
283 | ||
284 | string stack3 = stack1 + Environment.NewLine + stack2; | |
285 | ||
286 | StackTraceStringField.SetValue(e, stack3); | |
287 | } | |
288 | ||
289 | /// <summary> | |
290 | /// A simple expression visitor to replace the first parameter of a | |
291 | /// MethodCallExpression (the IQueryable<X>) | |
292 | /// </summary> | |
293 | protected class SimpleExpressionReplacer : ExpressionVisitor | |
294 | { | |
295 | public MethodCallExpression Call { get; set; } | |
296 | public IQueryable Queryable { get; set; } | |
297 | ||
298 | protected override Expression VisitMethodCall(MethodCallExpression node) | |
299 | { | |
300 | if (node == Call) | |
301 | { | |
302 | var arguments = node.Arguments.ToArray(); | |
303 | arguments[0] = Expression.Constant(Queryable); | |
304 | return Expression.Call(node.Object, node.Method, arguments); | |
305 | } | |
306 | ||
307 | return base.VisitMethodCall(node); | |
308 | } | |
309 | } | |
310 | } |