Advertisement
Guest User

SafeQueryable v0.1 Running with Scissors

a guest
Mar 7th, 2015
377
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 7.80 KB | None | 0 0
  1. /// <summary>
  2. /// v0.1 Codename: Running with Scissors
  3. ///
  4. /// Extension methods that try to execute a IQuerable&lt;T&gt; query
  5. /// on a provider that doesn't support some methods. The part that can
  6. /// be executed on the provider is executed there, the other part is
  7. /// executed locally.
  8. ///
  9. /// Note that it **won't** play nice with the Object Tracking!
  10. ///
  11. /// Not suitable for programmers under 5 years (of experience)!
  12. /// Dangerous if inhaled or executed.
  13. /// </summary>
  14. public static class SafeQueryable {
  15.     public class SafeQueryableInner<T> : IEnumerable<T> {
  16.         public readonly IQueryable<T> Query;
  17.         public IEnumerable<T> InnerEnumerable { get; protected set; }
  18.  
  19.         public SafeQueryableInner(IQueryable<T> query) {
  20.             Query = query;
  21.         }
  22.  
  23.         public IEnumerator<T> GetEnumerator() {
  24.             if (InnerEnumerable == null) {
  25.                 InnerEnumerable = CreateEnumerable();
  26.             }
  27.  
  28.             return InnerEnumerable.GetEnumerator();
  29.         }
  30.  
  31.         IEnumerator IEnumerable.GetEnumerator() {
  32.             return GetEnumerator();
  33.         }
  34.  
  35.         private IEnumerable<T> CreateEnumerable() {
  36.             try {
  37.                 IEnumerable<T> enumerable = Query.AsEnumerable();
  38.  
  39.                 // The NotSupportedException happens when the GetEnumerator is
  40.                 // called
  41.                 IEnumerator<T> enumerator = enumerable.GetEnumerator();
  42.  
  43.                 // SUCCESS :-)
  44.                 return new FakeEnumerable<T>(enumerator, enumerable);
  45.             } catch (NotSupportedException exception) {
  46.                 // TODO Log this "primary" exception. It will help you to optimize the query
  47.  
  48.                 // FAILURE :-(
  49.                 MethodInfo createEnumeratorMethod = typeof(SafeQueryableInner<T>).GetMethod("CreateSplittedEnumerable", BindingFlags.Public | BindingFlags.Instance);
  50.  
  51.                 MethodCallExpression call;
  52.                 Expression innerExpression = Query.Expression;
  53.  
  54.                 while ((call = innerExpression as MethodCallExpression) != null && call.Arguments.Count > 0 && (innerExpression = call.Arguments[0] as Expression) != null) {
  55.                     try {
  56.                         // We use reflection here, because CreateEnumerator was easier to build with Generics
  57.                         IEnumerable<T> enumerable = (IEnumerable<T>)createEnumeratorMethod.MakeGenericMethod(innerExpression.Type.GetGenericArguments()[0]).Invoke(this, new object[] { call, innerExpression });
  58.                         return enumerable;
  59.                     } catch (TargetInvocationException ex) {
  60.                         if (!(ex.InnerException is NotSupportedException)) {
  61.                             throw;
  62.                         }
  63.                     }
  64.                 }
  65.  
  66.                 throw;
  67.             }
  68.         }
  69.  
  70.         public IEnumerable<T> CreateSplittedEnumerable<TInner>(MethodCallExpression call, Expression innerExpression) {
  71.             IEnumerable<TInner> enumerable = Query.Provider.CreateQuery<TInner>(innerExpression);
  72.  
  73.             // We need to ask for the GetEnumerator to be able to check the query
  74.             // This call will throw if the LINQ-to-SQL can't produce the query
  75.             IEnumerator<TInner> enumerator = enumerable.GetEnumerator();
  76.  
  77.             // Success!
  78.  
  79.             // We transform back the enumerator to an enumerable
  80.             IEnumerable<TInner> enumerable2 = new FakeEnumerable<TInner>(enumerator, enumerable);
  81.  
  82.             // "Quick-n-dirty". We use a fake Queryable. The "right" way would probably be
  83.             // transform all the outer query from IQueryable.Method<T> to IEnumerable.Method<T>
  84.             // Too much long :)
  85.             // Note that this Queryable is executed in "local" (the AsQueryable<T> method is nearly
  86.             // useless... The second time in my life I was able to use it for something)
  87.             IQueryable<TInner> queryable = Queryable.AsQueryable(enumerable2);
  88.  
  89.             // We rebuild a new expression by changing the "old" inner parameter
  90.             // of the MethodCallExpression with the queryable we just
  91.             // built
  92.             Expression expression2 = new SimpleExpressionReplacer { Call = call, Queryable = queryable }.Visit(Query.Expression);
  93.  
  94.             // We "execute" locally the whole query through a second
  95.             // "outer" instance of the EnumerableQuery (this class is
  96.             // the class that "implements" the "fake-magic" of AsQueryable)
  97.             EnumerableQuery<T> query2 = new EnumerableQuery<T>(expression2);
  98.            
  99.             // And we return an enumerator to it
  100.             return query2.AsEnumerable();
  101.         }
  102.  
  103.         /// <summary>
  104.         /// A fake enumerator that "caches" in advance the first enumerator
  105.         /// and if requested for a second enumerator creates it from an
  106.         /// enumerable
  107.         /// </summary>
  108.         /// <typeparam name="T"></typeparam>
  109.         public class FakeEnumerable<T> : IEnumerable<T> {
  110.             public IEnumerable<T> Enumerable { get; set; }
  111.             private IEnumerator<T> Enumerator { get; set; }
  112.  
  113.             /// <summary>
  114.             /// We already have a enumerator that we can return when asked (called enumerator).
  115.             /// If asked for a second enumerator, we build it from the enumerable
  116.             /// </summary>
  117.             /// <param name="enumerator"></param>
  118.             /// <param name="enumerable"></param>
  119.             public FakeEnumerable(IEnumerator<T> enumerator, IEnumerable<T> enumerable) {
  120.                 Enumerable = enumerable;
  121.                 Enumerator = enumerator;
  122.             }
  123.  
  124.             public IEnumerator<T> GetEnumerator() {
  125.                 if (Enumerator == null) {
  126.                     return Enumerable.GetEnumerator();
  127.                 }
  128.  
  129.                 IEnumerator<T> enumerator = Enumerator;
  130.                 Enumerator = null;
  131.                 return enumerator;
  132.             }
  133.  
  134.             IEnumerator IEnumerable.GetEnumerator() {
  135.                 return GetEnumerator();
  136.             }
  137.         }
  138.  
  139.         /// <summary>
  140.         /// A simple expression visitor to replace the first parameter
  141.         /// of a MethodCall (the IQueryable&lt;X&gt;
  142.         /// </summary>
  143.         public class SimpleExpressionReplacer : ExpressionVisitor {
  144.             public MethodCallExpression Call { get; set; }
  145.             public object Queryable { get; set; }
  146.  
  147.             protected override Expression VisitMethodCall(MethodCallExpression node) {
  148.                 if (node == Call) {
  149.                     var arguments = node.Arguments.ToArray();
  150.                     arguments[0] = Expression.Constant(Queryable);
  151.                     return Expression.Call(node.Object, node.Method, arguments);
  152.                 }
  153.  
  154.                 return base.VisitMethodCall(node);
  155.             }
  156.         }
  157.     }
  158.  
  159.     public static IEnumerable<TElement> AsSafeEnumerable<TElement>(this IQueryable<TElement> query) {
  160.         return new SafeQueryableInner<TElement>(query);
  161.     }
  162.  
  163.     public static List<TElement> ToSafeList<TElement>(this IQueryable<TElement> query) {
  164.         return query.AsSafeEnumerable().ToList();
  165.     }
  166.  
  167.     public static TElement[] ToSafeArray<TElement>(this IQueryable<TElement> query) {
  168.         return query.AsSafeEnumerable().ToArray();
  169.     }
  170.  
  171.     public static TElement SafeFirst<TElement>(this IQueryable<TElement> query) {
  172.         return query.AsSafeEnumerable().First();
  173.     }
  174.  
  175.     public static TElement SafeFirst<TElement>(this IQueryable<TElement> query, Expression<Func<TElement, bool>> predicate) {
  176.         return query.Where(predicate).AsSafeEnumerable().First();
  177.     }
  178.  
  179.     public static TElement SafeFirst<TElement>(this IQueryable<TElement> query, Func<TElement, bool> predicate) {
  180.         return query.AsSafeEnumerable().First(predicate);
  181.     }
  182. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement