简体   繁体   中英

IQueryable failback to IEnumerable

Having IQueryable<T> returned from Linq-2-SQL data context is it possible to wrap it with custom IQueryable<T> implementation that on execution stage (for instance, while enumeration) will handle underlying IQueryProvider 's NotSupportedException and failback to enumerating whole data context and applying Expression using in-memory IQueryProvider ( System.Linq.EnumerableQuery<T> )?

Code example:

IQueryable<User> users = usersRepository.GetAll();

// linq-to-sql can handle it    
users.Where(u => u.Id > 10).ToList(); 

// linq-to-sql IQueryProvider will throw NotSupportedException in run-time,
// because ComplexFilter body was not captured as Expression
users.Where(u => ComplexFilter(u)).ToList(); 

// will succeed, because EnumerableQuery do not need Expression Tree
users.AsEnumerable().Where(u => ComplexFilter(u)).ToList(); 

public static bool ComplexFilter(User user)
{
    return user.Id + user.Name.Length > 12;
}

I looking for way to wrap IQueryable that will automatically failback to IEnumerable if underlying IQueryProvider throws NotSupportedException:

IQueryable<User> safeUsersProxy = new SafeUsersProxy<User>(users);

// I want it to try underlying linq-to-sql provider first
// and then automatically failback to IEnumerable and return
// the result anyway (do not care about whole data enumerating in such cases)
safeUsersProxy.Where(u => ComplexFilter(u)).ToList(); 

The point

This will allow convenient way to execute all queries on DB level whenever possible , and only if Linq-to-SQL was unable to convert Expression into the SQL use slow EnumerableQuery fetching the whole dataset.

Now, I always thought that if someone asks you some rope to hang himself you should only ask two questions, "are you sure?" and "how much rope do you need?". So you ask me for some rope, you told us you are sure you want this rope, so who am I to not give you this rope?

This is what you asked... Let's call it a v0.5 . It even has a codename: Armed and Dangerous . I've even given to v0.1 a codename: Running with Scissors . It is disponible at http://pastebin.com/6qLs8TPt . (the v0.2 is Lost in Space , because I've forgot it on another computer :-) ). I've added to the v0.3 Playing with Fire http://pastebin.com/pRbKt1Z2 a simple logger and a Stack Trace augmenter, and to the v0.4 Crossing without Looking http://pastebin.com/yEhc9vjg a little EF compatibility.

I checked it with simple queries and Join(s). It seems to work. Much could be added. For example an "intelligent" projector that analyzes the Select that probably is removed from the query and tries to rebuild it... But this can be a good start. There is a small problem: as I've written in a comment, depending on the presence/not presence of a Select , the LINQ-to-SQL loads the returned objects in its Object Tracker. If in the "original" query there is a Select (so no object tracking), and I remove it to execute it locally, then the full object will be loaded, and the object tracking will be turned "on". Clearly you can context.ObjectTrackingEnabled = false .

Now it should be compatible with simple EF queries. Not compatible with AsNoTracking() / Include() .

The NotSupportedException is tracked in an Exception property. Note that this property is modified only in the most "external" IQueryable. So

// Optional :-) You can ignore the logger and live happy...
// Not optional: you can live happy very far from me!
SafeQueryable.Logger = (iqueriable, expression, e) => Console.WriteLine(e);

var q1 = context.Items.AsSafe();
var q2 = q1.Where(x => x.ID.GetHashCode() > 0);
var q3 = q2.Select(x => x.ID);

var ex = ((ISafeQueryable)q3).Exception;

How to use it? There is a single extension method, AsSafe() . You use it on a IQueryable<T> and then you execute the query... And live happy... Playing with Fire ! :-)

Examples of use:

// Queries that are NotSupportedException with LINQ-to-SQL
var q1 = context.Items.AsSafe().Where(x => x.ID.GetHashCode() > 0).Take(2).Select(x => x.ID);
var q2 = context.Items.Where(x => x.ID.GetHashCode() > 0).AsSafe().Take(2).Select(x => x.ID);
var q3 = context.Items.Where(x => x.ID.GetHashCode() > 0).Take(2).AsSafe().Select(x => x.ID);
var q4 = context.Items.Where(x => x.ID.GetHashCode() > 0).Take(2).Select(x => x.ID).AsSafe();

//// Queries that are OK with LINQ-to-SQL
//var q1 = context.Items.AsSafe().Where(x => x.ID > 0).Take(2).Select(x => x.ID);
//var q2 = context.Items.Where(x => x.ID > 0).AsSafe().Take(2).Select(x => x.ID);
//var q3 = context.Items.Where(x => x.ID > 0).Take(2).AsSafe().Select(x => x.ID);
//var q4 = context.Items.Where(x => x.ID > 0).Take(2).Select(x => x.ID).AsSafe();

var r1 = q1.ToList();
var r2 = q2.First();
var r3 = q3.Max();
// The Aggregate isn't normally supported by LINQ-to-SQL
var r4 = q4.Aggregate((x, y) => x + y);

var ex1 = ((ISafeQueryable)q1).Exception;
var ex2 = ((ISafeQueryable)q2).Exception;
var ex3 = ((ISafeQueryable)q3).Exception;
var ex4 = ((ISafeQueryable)q4).Exception;

As you can see, you can use the AsSafe() at any step, and it will work. This happens because the query is wrapped by AsSafe() in a SafeQueryable<T> that is both a IQueryProvider and a IQueryable<T> (like the classes of LINQ-to-SQL). In this way every other IQueryable method you call on it will produce other SafeQueryable<T> objects (so it self-reproduce :-) )

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

/// <summary>
/// v0.5 Codename: Armed and Dangerous
/// 
/// (previous version v0.1 Codename: Running with Scissors: http://pastebin.com/6qLs8TPt)
/// (previous version v0.2 Codename: Lost in Space: lost in space :-) )
/// (previous version v0.3 Codename: Playing with Fire: http://pastebin.com/pRbKt1Z2)
/// (previous version v0.4 Codename: Crossing without Looking: http://pastebin.com/yEhc9vjg)
/// 
/// Support class with an extension method to make "Safe" an IQueryable 
/// or IQueryable&lt;T&gt;. Safe as "I work for another company, thousand 
/// of miles from you. I do hope I won't ever buy/need something from 
/// your company".
/// The Extension methods wraps a IQueryable in a wrapper that then can
/// be used to execute a query. If the original Provider doesn't suppport
/// some methods, the query will be partially executed by the Provider 
/// and partially executed locally.
///
/// Minimal support for EF.
/// 
/// Note that this **won't** play nice with the Object Tracking!
///
/// Not suitable for programmers under 5 years (of experience)!
/// Dangerous if inhaled or executed.
/// </summary>
public static class SafeQueryable
{
    /// <summary>
    /// Optional logger to log the queries that are "corrected. Note that
    /// there is no "strong guarantee" that the IQueriable (that is also 
    /// an IQueryProvider) is executing its (as in IQueriable.Expression)
    /// Expression, so an explicit Expression parameter is passed. This
    /// because the IQueryProvider.Execute method receives an explicit
    /// expression parameter. Clearly there is a "weak guarantee" that
    /// unless you do "strange things" this won't happen :-)
    /// </summary>
    public static Action<IQueryable, Expression, NotSupportedException> Logger { get; set; }

    /// <summary>
    /// Return a "Safe" IQueryable&lt;T&gt;. 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"></param>
    /// <returns></returns>
    public static IQueryable<T> AsSafe<T>(this IQueryable<T> source)
    {
        if (source is SafeQueryable<T>)
        {
            return source;
        }

        return new SafeQueryable<T>(source);
    }
}

/// <summary>
/// Simple interface useful to collect the Exception, or to recognize
/// a SafeQueryable&lt;T&gt;.
/// </summary>
public interface ISafeQueryable
{
    NotSupportedException Exception { get; }
}

/// <summary>
/// "Safe" wrapper around a IQueryable&lt;T;&gt;
/// </summary>
/// <typeparam name="T"></typeparam>
public class SafeQueryable<T> : IOrderedQueryable<T>, IQueryProvider, ISafeQueryable
{
    protected static readonly FieldInfo StackTraceStringField = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);

    // The query. Note that it can be "transformed" to a "safe" version
    // of itself. When it happens, IsSafe becomes true
    public IQueryable<T> Query { get; protected set; }

    // IsSafe means that the query has been "corrected" if necessary and
    // won't throw a NotSupportedException
    protected bool IsSafe { get; set; }

    // Logging of the "main" NotSupportedException.
    public NotSupportedException Exception { get; protected set; }

    public SafeQueryable(IQueryable<T> query)
    {
        Query = query;
    }

    /* IQueryable<T> */

    public IEnumerator<T> GetEnumerator()
    {
        if (IsSafe)
        {
            return Query.GetEnumerator();
        }

        return new SafeEnumerator(this);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public Type ElementType
    {
        get { return Query.ElementType; }
    }

    public Expression Expression
    {
        get { return Query.Expression; }
    }

    public IQueryProvider Provider
    {
        get { return this; }
    }

    /* IQueryProvider */

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return CreateQueryImpl<TElement>(expression);
    }

    public IQueryable CreateQuery(Expression expression)
    {
        Type iqueryableArgument = GetIQueryableTypeArgument(expression.Type);
        MethodInfo createQueryImplMethod = typeof(SafeQueryable<T>)
            .GetMethod("CreateQueryImpl", BindingFlags.Instance | BindingFlags.NonPublic)
            .MakeGenericMethod(iqueryableArgument);

        return (IQueryable)createQueryImplMethod.Invoke(this, new[] { expression });
    }

    public TResult Execute<TResult>(Expression expression)
    {
        return ExecuteImpl<TResult>(expression);
    }

    public object Execute(Expression expression)
    {
        Type iqueryableArgument = GetIQueryableTypeArgument(expression.Type);
        MethodInfo executeImplMethod = typeof(SafeQueryable<T>)
            .GetMethod("ExecuteImpl", BindingFlags.Instance | BindingFlags.NonPublic)
            .MakeGenericMethod(iqueryableArgument);

        return executeImplMethod.Invoke(this, new[] { expression });
    }

    /* Implementation methods */

    // Gets the T of IQueryablelt;T&gt;
    protected static Type GetIQueryableTypeArgument(Type type)
    {
        IEnumerable<Type> interfaces = type.IsInterface ?
            new[] { type }.Concat(type.GetInterfaces()) :
            type.GetInterfaces();
        Type argument = (from x in interfaces
                         where x.IsGenericType
                         let gt = x.GetGenericTypeDefinition()
                         where gt == typeof(IQueryable<>)
                         select x.GetGenericArguments()[0]).FirstOrDefault();
        return argument;
    }

    protected IQueryable<TElement> CreateQueryImpl<TElement>(Expression expression)
    {
        return new SafeQueryable<TElement>(Query.Provider.CreateQuery<TElement>(expression));
    }

    protected TResult ExecuteImpl<TResult>(Expression expression)
    {
        if (IsSafe && Query.Expression == expression)
        {
            TResult result = Query.Provider.Execute<TResult>(expression);
            return result;
        }

        try
        {
            // Note that thanks to how everything knits together, if you
            // call query1.First(); query1.First(); the second call will
            // get to use the query cached by the first one (technically
            // the cached query will be only the "query1" part)

            // We try executing it directly
            TResult result = Query.Provider.Execute<TResult>(expression);

            // Success!
            if (!IsSafe && CanCache(expression, true))
            {
                IsSafe = true;
            }

            return result;
        }
        catch (NotSupportedException e1)
        {
            // Clearly there was a NotSupportedException :-)
            Tuple<IEnumerator<T>, bool, TResult> result = HandleEnumerationFailure<TResult>(e1, expression, true);

            if (result == null)
            {
                throw;
            }

            // Success!
            return result.Item3;
        }
    }

    // Is used both indirectly by GetEnumerator() and by Execute<>.
    // The returned Tuple<,,> has the first two elements that are valid
    // when used by the GetEnumerator() and the last that is valid
    // when used by Execute<>.
    protected Tuple<IEnumerator<T>, bool, TResult> HandleEnumerationFailure<TResult>(NotSupportedException e1, Expression expression, bool singleResult)
    {
        // We "augment" the exception with the full stack trace
        AugmentStackTrace(e1, 3);

        if (SafeQueryable.Logger != null)
        {
            SafeQueryable.Logger(this, expression, e1);
        }

        // We save this first exception
        Exception = e1;

        {
            var query = Query;

            MethodInfo executeSplittedMethod = typeof(SafeQueryable<T>).GetMethod("ExecuteSplitted", BindingFlags.Instance | BindingFlags.NonPublic);

            MethodCallExpression call;
            Expression innerExpression = expression;
            Type iqueryableArgument;

            // We want to check that there is a MethodCallExpression with 
            // at least one argument, and that argument is an Expression
            // of type IQueryable<iqueryableArgument>, and we save the
            // iqueryableArgument
            while ((call = innerExpression as MethodCallExpression) != null &&
                call.Arguments.Count > 0 &&
                (innerExpression = call.Arguments[0] as Expression) != null &&
                (iqueryableArgument = GetIQueryableTypeArgument(innerExpression.Type)) != null)
            {
                try
                {
                    Tuple<IEnumerator<T>, bool, TResult> result2 = (Tuple<IEnumerator<T>, bool, TResult>)executeSplittedMethod.MakeGenericMethod(iqueryableArgument, typeof(TResult)).Invoke(this, new object[] { expression, call, innerExpression, singleResult });
                    return result2;
                }
                catch (TargetInvocationException e2)
                {
                    if (!(e2.InnerException is NotSupportedException))
                    {
                        throw;
                    }
                }
            }

            return null;
        }
    }

    // Is used both indirectly by GetEnumerator() and by Execute<>.
    // The returned Tuple<,,> has the first two elements that are valid
    // when used by the GetEnumerator() and the last that is valid
    // when used by Execute<>.
    protected Tuple<IEnumerator<T>, bool, TResult> ExecuteSplitted<TInner, TResult>(Expression expression, MethodCallExpression call, Expression innerExpression, bool singleResult)
    {
        // The NotSupportedException should happen here
        IQueryable<TInner> innerQueryable = Query.Provider.CreateQuery<TInner>(innerExpression);

        // We try executing it directly
        IEnumerator<TInner> innerEnumerator = innerQueryable.GetEnumerator();

        bool moveNextSuccess = innerEnumerator.MoveNext();
        IEnumerator<T> enumerator;
        TResult singleResultValue;

        // Success!

        {
            // Now we wrap the partially used enumerator in an
            // EnumerableFromStartedEnumerator
            IEnumerable<TInner> innerEnumerable = new EnumerableFromStartedEnumerator<TInner>(innerEnumerator, moveNextSuccess, innerQueryable);

            // Then we apply an AsQueryable, that does some magic
            // to make the query appear to be a Queryable
            IQueryable<TInner> innerEnumerableAsQueryable = Queryable.AsQueryable(innerEnumerable);

            // We rebuild a new expression by changing the "old" 
            // inner parameter of the MethodCallExpression with the 
            // queryable we just built
            var arguments = call.Arguments.ToArray();
            arguments[0] = Expression.Constant(innerEnumerableAsQueryable);
            MethodCallExpression call2 = Expression.Call(call.Object, call.Method, arguments);
            Expression expressionWithFake = new SimpleExpressionReplacer(call, call2).Visit(expression);

            // We "execute" locally the whole query through a second 
            // "outer" instance of the EnumerableQuery (this class is 
            // the class that "implements" the "fake-magic" of 
            // AsQueryable)
            IQueryable<T> queryable = new EnumerableQuery<T>(expressionWithFake);

            if (singleResult)
            {
                enumerator = null;
                moveNextSuccess = false;
                singleResultValue = queryable.Provider.Execute<TResult>(queryable.Expression);
            }
            else
            {
                enumerator = queryable.GetEnumerator();
                moveNextSuccess = enumerator.MoveNext();
                singleResultValue = default(TResult);
            }
        }

        // We could enter here with a new query from Execute<>(),
        // with IsSafe == true . It would be useless to try to cache
        // that query.
        if (!IsSafe && CanCache(expression, singleResult))
        {
            Stopwatch sw = Stopwatch.StartNew();

            // We redo the same things to create a second copy of
            // the query that is "complete", not partially 
            // enumerated. This second copy will be cached in the
            // SafeQueryable<T>.
            // Note that forcing the Queryable.AsQueryable to not
            // "recast" the query to the original IQueryable<T> is
            // quite complex :-) We have to 
            // .AsEnumerable().Select(x => x) .
            IEnumerable<TInner> innerEnumerable = innerQueryable.AsEnumerable().Select(x => x);
            IQueryable<TInner> innerEnumerableAsQueryable = Queryable.AsQueryable(innerEnumerable);

            // Note that we cache the SafeQueryable<>.Expression!
            var arguments = call.Arguments.ToArray();
            arguments[0] = Expression.Constant(innerEnumerableAsQueryable);
            MethodCallExpression call2 = Expression.Call(call.Object, call.Method, arguments);
            Expression expressionWithFake = new SimpleExpressionReplacer(call, call2).Visit(Expression);

            IQueryable<T> queryable = new EnumerableQuery<T>(expressionWithFake);

            // Now the SafeQueryable<T> has a query that *just works*
            Query = queryable;
            IsSafe = true;

            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks);
        }

        return Tuple.Create(enumerator, moveNextSuccess, singleResultValue);
    }

    protected bool CanCache(Expression expression, bool singleResult)
    {
        // GetEnumerator() doesn't permit changing the query
        if (!singleResult)
        {
            return true;
        }

        // The expression is equal to the one in Query.Expression
        // (should be very rare!)
        if (Query.Expression == expression)
        {
            return true;
        }

        MethodCallExpression call;
        Expression innerExpression = expression;
        Type iqueryableArgument;

        // We walk back the expression to see if a smaller part of it is
        // the "original" Query.Expression . This happens for example 
        // when one of the operators that returns a single value 
        // (.First(), .FirstOrDefault(), .Single(), .SingleOrDefault(),
        // .Any(), .All()., .Min(), .Max(), ...) are used.
        while ((call = innerExpression as MethodCallExpression) != null &&
            call.Arguments.Count > 0 &&
            (innerExpression = call.Arguments[0] as Expression) != null &&
            (iqueryableArgument = GetIQueryableTypeArgument(innerExpression.Type)) != null)
        {
            if (Query.Expression == innerExpression)
            {
                return true;
            }
        }

        return false;
    }

    // The StackTrace of an Exception "stops" at the catch. This method
    // "augments" it to include the full stack trace.
    protected static void AugmentStackTrace(Exception e, int skipFrames = 2)
    {
        // Playing with a private field here. Don't do it at home :-)
        // If not present, do nothing.
        if (StackTraceStringField == null)
        {
            return;
        }

        string stack1 = e.StackTrace;
        string stack2 = new StackTrace(skipFrames, true).ToString();

        string stack3 = stack1 + stack2;

        StackTraceStringField.SetValue(e, stack3);
    }

    /* Utility classes */

    // An IEnumerator<T> that applies the AsSafe() paradigm, knowing that
    // normally the exception happens only on the first MoveFirst().
    protected class SafeEnumerator : IEnumerator<T>
    {
        protected readonly SafeQueryable<T> SafeQueryable_;

        protected IEnumerator<T> Enumerator { get; set; }

        public SafeEnumerator(SafeQueryable<T> safeQueryable)
        {
            SafeQueryable_ = safeQueryable;
        }

        public T Current
        {
            get
            {
                return Enumerator != null ? Enumerator.Current : default(T);
            }
        }

        public void Dispose()
        {
            if (Enumerator != null)
            {
                Enumerator.Dispose();
            }
        }

        object IEnumerator.Current
        {
            get { return Current; }
        }

        public bool MoveNext()
        {
            // We handle exceptions only on first MoveNext()
            if (Enumerator != null)
            {
                return Enumerator.MoveNext();
            }

            try
            {
                // We try executing it directly
                Enumerator = SafeQueryable_.Query.GetEnumerator();
                bool result = Enumerator.MoveNext();

                // Success!
                SafeQueryable_.IsSafe = true;
                return result;
            }
            catch (NotSupportedException e1)
            {
                // Clearly there was a NotSupportedException :-)
                Tuple<IEnumerator<T>, bool, T> result = SafeQueryable_.HandleEnumerationFailure<T>(e1, SafeQueryable_.Query.Expression, false);

                if (result == null)
                {
                    throw;
                }

                Enumerator = result.Item1;
                return result.Item2;
            }
        }

        public void Reset()
        {
            if (Enumerator != null)
            {
                Enumerator.Reset();
            }
        }
    }
}

// A simple expression visitor to replace some nodes of an expression 
// with some other nodes
public class SimpleExpressionReplacer : ExpressionVisitor
{
    public readonly Dictionary<Expression, Expression> Replaces;

    public SimpleExpressionReplacer(Dictionary<Expression, Expression> replaces)
    {
        Replaces = replaces;
    }

    public SimpleExpressionReplacer(IEnumerable<Expression> from, IEnumerable<Expression> to)
    {
        Replaces = new Dictionary<Expression, Expression>();

        using (var enu1 = from.GetEnumerator())
        using (var enu2 = to.GetEnumerator())
        {
            while (true)
            {
                bool res1 = enu1.MoveNext();
                bool res2 = enu2.MoveNext();

                if (!res1 || !res2)
                {
                    if (!res1 && !res2)
                    {
                        break;
                    }

                    if (!res1)
                    {
                        throw new ArgumentException("from shorter");
                    }

                    throw new ArgumentException("to shorter");
                }

                Replaces.Add(enu1.Current, enu2.Current);
            }
        }
    }

    public SimpleExpressionReplacer(Expression from, Expression to)
    {
        Replaces = new Dictionary<Expression, Expression> { { from, to } };
    }

    public override Expression Visit(Expression node)
    {
        Expression to;

        if (node != null && Replaces.TryGetValue(node, out to))
        {
            return base.Visit(to);
        }

        return base.Visit(node);
    }
}

// Simple IEnumerable<T> that "uses" an IEnumerator<T> that has
// already received a MoveNext(). "eats" the first MoveNext() 
// received, then continues normally. For shortness, both IEnumerable<T>
// and IEnumerator<T> are implemented by the same class. Note that if a
// second call to GetEnumerator() is done, the "real" IEnumerator<T> will
// be returned, not this proxy implementation.
public class EnumerableFromStartedEnumerator<T> : IEnumerable<T>, IEnumerator<T>
{
    public readonly IEnumerator<T> Enumerator;

    public readonly IEnumerable<T> Enumerable;

    // Received by creator. Return value of MoveNext() done by caller
    protected bool FirstMoveNextSuccessful { get; set; }

    // The Enumerator can be "used" only once, then a new enumerator
    // can be requested by Enumerable.GetEnumerator() 
    // (default = false)
    protected bool Used { get; set; }

    // The first MoveNext() has been already done (default = false)
    protected bool DoneMoveNext { get; set; }

    public EnumerableFromStartedEnumerator(IEnumerator<T> enumerator, bool firstMoveNextSuccessful, IEnumerable<T> enumerable)
    {
        Enumerator = enumerator;
        FirstMoveNextSuccessful = firstMoveNextSuccessful;
        Enumerable = enumerable;
    }

    public IEnumerator<T> GetEnumerator()
    {
        if (Used)
        {
            return Enumerable.GetEnumerator();
        }

        Used = true;
        return this;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public T Current
    {
        get
        {
            // There are various school of though on what should
            // happens if called before the first MoveNext() or
            // after a MoveNext() returns false. We follow the 
            // "return default(TInner)" school of thought for the
            // before first MoveNext() and the "whatever the 
            // Enumerator wants" for the after a MoveNext() returns
            // false
            if (!DoneMoveNext)
            {
                return default(T);
            }

            return Enumerator.Current;
        }
    }

    public void Dispose()
    {
        Enumerator.Dispose();
    }

    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }

    public bool MoveNext()
    {
        if (!DoneMoveNext)
        {
            DoneMoveNext = true;
            return FirstMoveNextSuccessful;
        }

        return Enumerator.MoveNext();
    }

    public void Reset()
    {
        // This will 99% throw :-) Not our problem.
        Enumerator.Reset();

        // So it is improbable we will arrive here
        DoneMoveNext = true;
    }
}

Note that there is a bug in the LINQ-to-SQL:

var q5 = context.Items.Where(x => x.ID > context.Nodis.Min(y => y.ID.GetHashCode()));
var r5 = q5.ToList();

This won't throw NotSupportedException but won't execute correctly. I think it could be a problem with many queries that use the context.SomeTable "internally" in the query.

If I'm not mistaken, you need to materialize the collection through .ToList () before using your method. Try to convert the collection into a list and store it in a variable. After that, try to use his method in the collection that was generated in this variable.

I don't think there's an automatic way to do this, other than writing your own ExtressionTree

But analyzing your code I think that what you need is to use an Extension Method to encapsulate your query

Something like this (I already tested the concept using LinqToSql and it supports the Length property):

public static class Extensions
{
    public static IQueryable<User> WhereUserMatches(this IQueryable<User> source)
    {
        return source.Where(x => x.Id + x.Name.Length > 12);
    }
}

Usage

var myFileterdUsers = users.WhereUserMatches();

And since you are working with IQueryable then your condition will be sent to the server not in memory

With this approach you can encapsulate your complex queries and actually execute them in the server instead of memory.

A word about hiding the Exception

If the query cannot be handled by the provider I wouldn't recommend you to hide the exception by falling back to use IEnumerable (in memory query) because this could cause undesired behavior in your application

I'd recommend you to be explicit and throw an exception if the underlying query provider cannot convert it to a valid ExpressionTree

Exceptions are good, they are our friends, they let us know if we are doing something wrong

Hiding the exception is the opposite and it's considered a bad practice, hiding the exception will give you the ilusion that your application works even when most likely it won't do exactly what you think it does

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM