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