View difference between Paste ID: yEhc9vjg and pRbKt1Z2
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&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
}