首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将IQueryable与IEnumerable连接为IQueryable

将IQueryable与IEnumerable连接为IQueryable
EN

Stack Overflow用户
提问于 2014-09-15 19:09:42
回答 1查看 958关注 0票数 4

在过去的几天里,我一直在互联网上搜索这个问题的解决方案,却没有找到我想要的东西。基本上,我的问题是:

  1. 我有一个需要实现的接口,它有一个返回IQueryable的方法(我没有访问接口的权限,所以不能更改这个接口)
  2. 我希望该方法返回(a)指向非常大的数据库表的IQueryable和(b)在同一实体类型的内存中计算的大型IEnumerable的连接。
  3. 我无法执行queryableA.Concat(enumerableB).Where(condition),因为它将尝试将整个数组发送到服务器(除此之外,我还得到了一个只支持基本类型的异常)。
  4. 我不能执行enumerableB.Concat(queryableA).Where(condition),因为它会将整个表拖到内存中,并将其作为IEnumerable处理。

因此,经过一些搜索之后,我想我已经决定了解决这个问题的一个很好的方法,就是编写我自己的ConcatenatingQueryable IQueryable实现,它需要两个IQueryable的实现,并在每个独立的IQueryable上执行表达式树,然后连接结果。但是,当它返回堆栈溢出时,我似乎遇到了问题。基于http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx,到目前为止我已经实现了以下内容:

代码语言:javascript
复制
class Program
{
    static void Main(string[] args)
    {
        var source1 = new[] {  1, 2 }.AsQueryable();
        var source2 = new[] { -1, 1 }.AsQueryable();
        var matches = new ConcatenatingQueryable<int>(source1, source2).Where(x => x <= 1).ToArray();
        Console.WriteLine(string.Join(",", matches));
        Console.ReadKey();
    }

    public class ConcatenatingQueryable<T> : IQueryable<T>
    {
        private readonly ConcatenatingQueryableProvider<T> provider;
        private readonly Expression expression;

        public ConcatenatingQueryable(IQueryable<T> source1, IQueryable<T> source2)
            : this(new ConcatenatingQueryableProvider<T>(source1, source2))
        {}

        public ConcatenatingQueryable(ConcatenatingQueryableProvider<T> provider)
        {
            this.provider = provider;
            this.expression = Expression.Constant(this);
        }

        public ConcatenatingQueryable(ConcatenatingQueryableProvider<T> provider, Expression expression)
        {
            this.provider = provider;
            this.expression = expression;
        }

        Expression IQueryable.Expression
        {
            get { return expression; }
        }

        Type IQueryable.ElementType
        {
            get { return typeof(T); }
        }

        IQueryProvider IQueryable.Provider
        {
            get { return provider; }
        }

        public IEnumerator<T> GetEnumerator()
        {
            // This line is calling Execute below
            return ((IEnumerable<T>)provider.Execute(expression)).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)provider.Execute(expression)).GetEnumerator();
        }
    }

    public class ConcatenatingQueryableProvider<T> : IQueryProvider
    {
        private readonly IQueryable<T> source1;
        private readonly IQueryable<T> source2;

        public ConcatenatingQueryableProvider(IQueryable<T> source1, IQueryable<T> source2)
        {
            this.source1 = source1;
            this.source2 = source2;
        }

        IQueryable<TS> IQueryProvider.CreateQuery<TS>(Expression expression)
        {
            var elementType = TypeSystem.GetElementType(expression.Type);
            try
            {
                return (IQueryable<TS>)Activator.CreateInstance(typeof(ConcatenatingQueryable<>).MakeGenericType(elementType), new object[] { this, expression });
            }
            catch (TargetInvocationException tie)
            {
                throw tie.InnerException;
            }
        }

        IQueryable IQueryProvider.CreateQuery(Expression expression)
        {
            var elementType = TypeSystem.GetElementType(expression.Type);
            try
            {
                return (IQueryable)Activator.CreateInstance(typeof(ConcatenatingQueryable<>).MakeGenericType(elementType), new object[] { this, expression });
            }
            catch (TargetInvocationException tie)
            {
                throw tie.InnerException;
            }
        }

        TS IQueryProvider.Execute<TS>(Expression expression)
        {
            return (TS)Execute(expression);
        }

        object IQueryProvider.Execute(Expression expression)
        {
            return Execute(expression);
        }

        public object Execute(Expression expression)
        {
            // This is where I suspect the problem lies, as executing the 
            // Expression.Constant from above here will call Enumerate again,
            // which then calls this, and... you get the point
            dynamic results1 = source1.Provider.Execute(expression);
            dynamic results2 = source2.Provider.Execute(expression);
            return results1.Concat(results2);
        }
    }

    internal static class TypeSystem
    {
        internal static Type GetElementType(Type seqType)
        {
            var ienum = FindIEnumerable(seqType);
            if (ienum == null)
                return seqType;
            return ienum.GetGenericArguments()[0];
        }

        private static Type FindIEnumerable(Type seqType)
        {
            if (seqType == null || seqType == typeof(string))
                return null;
            if (seqType.IsArray)
                return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType());
            if (seqType.IsGenericType)
            {
                foreach (var arg in seqType.GetGenericArguments())
                {
                    var ienum = typeof(IEnumerable<>).MakeGenericType(arg);
                    if (ienum.IsAssignableFrom(seqType))
                    {
                        return ienum;
                    }
                }
            }
            var ifaces = seqType.GetInterfaces();
            if (ifaces.Length > 0)
            {
                foreach (var iface in ifaces)
                {
                    var ienum = FindIEnumerable(iface);
                    if (ienum != null)
                        return ienum;
                }
            }
            if (seqType.BaseType != null && seqType.BaseType != typeof(object))
            {
                return FindIEnumerable(seqType.BaseType);
            }
            return null;
        }
    }
}

我对这个界面没有太多的经验,也有点不知所措。有人对如何做这件事有什么建议吗?如果需要的话,我也愿意完全放弃这种方法。

重申一下,我得到了一个StackOverflowException,堆栈跟踪只是上面两行注释行之间的一串调用,每对调用之间都有“外部代码”。我添加了一个示例Main方法,它使用了两个微小的枚举,但是您可以想象这些是更大的数据源,需要很长时间来枚举。

提前感谢您的帮助!

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-09-15 20:34:45

当您分解传递给IQueryProvider的表达式树时,您将看到LINQ方法的调用链。请记住,通常LINQ通过链接扩展方法工作,其中前一个方法的返回值作为第一个参数传递给下一个方法。

如果从逻辑上讲,这意味着链中的第一个LINQ方法必须有一个源参数,从代码中可以很清楚地看出,它的源代码实际上是最初启动整个过程的同一个IQueryable (您的ConcatenatingQueryable)。

当你建造这个的时候,你就有了正确的想法--你只需要走一小步就行了。我们需要做的是重新指出,首先LINQ方法使用实际的源,然后允许执行遵循它的自然路径。

下面是一些执行此操作的示例代码:

代码语言:javascript
复制
    public object Execute(Expression expression)
    {
        var query1 = ChangeQuerySource(expression, Expression.Constant(source1));
        var query2 = ChangeQuerySource(expression, Expression.Constant(source2));
        dynamic results1 = source1.Provider.Execute(query1);
        dynamic results2 = source2.Provider.Execute(query2);
        return Enumerable.Concat(results1, results2);
    }

    private static Expression ChangeQuerySource(Expression query, Expression newSource)
    {
        // step 1: cast the Expression as a MethodCallExpression.
        // This will usually work, since a chain of LINQ statements
        // is generally a chain of method calls, but I would not
        // make such a blind assumption in production code.
        var methodCallExpression = (MethodCallExpression)query;

        // step 2: Create a new MethodCallExpression, passing in
        // the existing one's MethodInfo so we're calling the same
        // method, but just changing the parameters. Remember LINQ
        // methods are extension methods, so the first argument is
        // always the source. We carry over any additional arguments.
        query = Expression.Call(
            methodCallExpression.Method,
            new Expression[] { newSource }.Concat(methodCallExpression.Arguments.Skip(1)));

        // step 3: We call .AsEnumerable() at the end, to get an
        // ultimate return type of IEnumerable<T> instead of
        // IQueryable<T>, so we can safely use this new expression
        // tree in any IEnumerable statement.
        query = Expression.Call(
            typeof(Enumerable).GetMethod("AsEnumerable", BindingFlags.Static | BindingFlags.Public)
            .MakeGenericMethod(
                TypeSystem.GetElementType(methodCallExpression.Arguments[0].Type)
            ),
            query);
        return query;
    }
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25855152

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档