简体   繁体   English

IQueryable故障回复到IEnumerable

[英]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> )? 从Linq-2-SQL数据上下文返回IQueryable<T>后,可以使用自定义IQueryable<T>实现来包装它 ,该实现在执行阶段(例如,枚举时)将处理基础 IQueryProviderNotSupportedException故障回复以枚举整个数据上下文和使用内存IQueryProviderSystem.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的方法,如果底层IQueryProvider抛出NotSupportedException,它将自动故障回复到IEnumerable:

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. 这将允许方便的方式在可能的情况下在DB级别执行所有查询,并且只有当Linq-to-SQL无法将Expression转换为SQL时才使用慢EnumerableQuery获取整个数据集。

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 . 这就是你问的......我们称之为v0.5 It even has a codename: Armed and Dangerous . 它甚至有一个代号: 武装和危险 I've even given to v0.1 a codename: Running with Scissors . 我甚至给了v0.1一个代号: 用剪刀跑 It is disponible at http://pastebin.com/6qLs8TPt . 它在http://pastebin.com/6qLs8TPt上是不可取的。 (the v0.2 is Lost in Space , because I've forgot it on another computer :-) ). v0.2 在太空中迷失了 ,因为我在另一台计算机上忘了它:-))。 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. 我已经添加到v0.3 Playing with Fire http://pastebin.com/pRbKt1Z2一个简单的记录器和一个Stack Trace增强器,并且添加到了v0.4 Crossing,而没有查看 http://pastebin.com/yEhc9vjg EF兼容性。

I checked it with simple queries and Join(s). 我用简单的查询和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. 例如,一个“智能”投影仪分析可能从查询中删除的Select并尝试重建它......但这可能是一个好的开始。 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. 有一个小问题:正如我在注释中写的那样,根据是否存在Select ,LINQ-to-SQL会在其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". 如果在“原始”查询中有一个Select (所以没有对象跟踪),并且我将其删除以在本地执行它,那么将加载完整对象,并且对象跟踪将被“打开”。 Clearly you can context.ObjectTrackingEnabled = false . 显然你可以context.ObjectTrackingEnabled = false

Now it should be compatible with simple EF queries. 现在它应该与简单的EF查询兼容。 Not compatible with AsNoTracking() / Include() . AsNoTracking() / Include()不兼容

The NotSupportedException is tracked in an Exception property. NotSupportedExceptionException属性中被跟踪。 Note that this property is modified only in the most "external" IQueryable. 请注意,此属性仅在最“外部”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() . 有一个扩展方法, AsSafe() You use it on a IQueryable<T> and then you execute the query... And live happy... Playing with Fire ! 你在IQueryable<T>上使用它,然后你执行查询...并且快乐地生活...... 玩火 :-) :-)

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. 如您所见,您可以在任何步骤使用AsSafe() ,它将起作用。 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). 发生这种情况是因为查询由AsSafe() SafeQueryable<T>中的AsSafe()包装, AsSafe()IQueryProvider又是IQueryable<T> (类似于LINQ-to-SQL的类)。 In this way every other IQueryable method you call on it will produce other SafeQueryable<T> objects (so it self-reproduce :-) ) 通过这种方式,您调用它的每个其他IQueryable方法将生成其他SafeQueryable<T>对象(因此它自我重现:-))

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: 请注意,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. 这不会抛出NotSupportedException但不会正确执行。 I think it could be a problem with many queries that use the context.SomeTable "internally" in the query. 我认为在查询中使用context.SomeTable “内部”的许多查询可能会出现问题。

If I'm not mistaken, you need to materialize the collection through .ToList () before using your method. 如果我没弄错的话,你需要在使用你的方法之前通过.ToList()实现集合。 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 除了编写自己的ExtressionTree之外,我认为没有一种自动方法可以做到这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): 像这样的东西(我已经使用LinqToSql测试了它的概念并且它支持Length属性):

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 由于您正在使用IQueryable因此您的条件将被发送到服务器而不是内存中

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 如果提供程序无法处理查询,我建议您不要通过回退使用IEnumerable (在内存查询中)来隐藏异常,因为这可能会导致应用程序中出现意外行为

I'd recommend you to be explicit and throw an exception if the underlying query provider cannot convert it to a valid ExpressionTree 如果基础查询提供程序无法将其转换为有效的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 隐藏例外是对面,它被认为是一种不好的做法,隐藏例外会给你,你的应用程序的工作即使是错觉时最有可能它不会做什么你认为它

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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