Advertisement
Guest User

SafeQueryable v0.3 Playing with Fire

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