Advertisement
Guest User

SafeQueryable v0.4 Crossing without Looking

a guest
Mar 19th, 2015
305
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /// <summary>
  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&lt;T&gt;. 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&lt;T&gt;.
  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&lt;T&gt;.
  55. /// </summary>
  56. public interface ISafeQueryable
  57. {
  58.     NotSupportedException Exception { get; }
  59. }
  60.  
  61. /// <summary>
  62. /// "Safe" wrapper around a IQueryable&lt;T;&gt;
  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&gt;
  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&lt;X&gt;)
  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. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement