Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/linq/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 将IQueryable与IEnumerable连接为IQueryable_C#_Linq_Iqueryable - Fatal编程技术网

C# 将IQueryable与IEnumerable连接为IQueryable

C# 将IQueryable与IEnumerable连接为IQueryable,c#,linq,iqueryable,C#,Linq,Iqueryable,在过去的几天里,我一直在互联网上寻找解决方案,但没有找到我想要的。基本上,我的问题是: 我有一个需要实现的接口,该接口有一个返回IQueryable的方法(我没有访问接口的权限,因此无法更改此接口) 我希望该方法返回(a)指向非常大的数据库表的IQueryable和(b)在相同实体类型的内存中计算的大型IEnumerable的串联 我无法执行queryableA.Concat(enumerableB).Where(条件),因为它将尝试将整个数组发送到服务器(除此之外,我得到一个例外,它只支持基本

在过去的几天里,我一直在互联网上寻找解决方案,但没有找到我想要的。基本上,我的问题是:

  • 我有一个需要实现的接口,该接口有一个返回IQueryable的方法(我没有访问接口的权限,因此无法更改此接口)
  • 我希望该方法返回(a)指向非常大的数据库表的IQueryable和(b)在相同实体类型的内存中计算的大型IEnumerable的串联
  • 我无法执行queryableA.Concat(enumerableB).Where(条件),因为它将尝试将整个数组发送到服务器(除此之外,我得到一个例外,它只支持基本类型)
  • 我不能执行enumerableB.Concat(queryableA).Where(条件),因为它会将整个表拉入内存并将其视为IEnumerable
  • 因此,经过一些搜索之后,我认为我已经决定了解决这个问题的一个好方法是编写我自己的IQueryable的ConcatenatingQueryable实现,它采用两个IQueryable并独立地在每个IQueryable上执行表达式树,然后将结果连接起来。然而,我似乎有问题,因为它返回堆栈溢出。基于,这是我迄今为止实现的:

    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;
            }
        }
    }
    
    类程序
    {
    静态void Main(字符串[]参数)
    {
    var source1=new[]{1,2}.AsQueryable();
    var source2=new[]{-1,1}.AsQueryable();
    var matches=new ConcatenatingQueryable(source1,source2)。其中(x=>x0)
    {
    foreach(iface中的var iface)
    {
    变量ienum=可计算(iface);
    如果(ienum!=null)
    回肠;
    }
    }
    if(seqType.BaseType!=null&&seqType.BaseType!=typeof(对象))
    {
    返回FindIEnumerable(seqType.BaseType);
    }
    返回null;
    }
    }
    }
    
    我对这个界面没有太多的经验,我有点不知所措。有人对此有什么建议吗?如果需要,我也愿意完全放弃这种方法

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


    非常感谢您的帮助

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

    如果我们从逻辑上遵循这一点,这意味着链中的第一个LINQ方法必须有一个源参数,从代码中可以清楚地看出,它的源实际上与最初启动整个过程的
    IQueryable
    (您的
    连接查询表
    )是同一个
    IQueryable

    当你建造这个的时候,你的想法基本上是正确的——你只需要再前进一小步。我们需要做的是重新指出第一个LINQ方法使用实际的源代码,然后允许执行遵循其自然路径

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

        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;
        }
    
    公共对象执行(表达式)
    {
    var query1=ChangeQuerySource(表达式,表达式.常量(source1));
    var query2=ChangeQuerySource(表达式,表达式.常量(source2));
    动态结果1=source1.Provider.Execute(query1);
    动态结果2=source2.Provider.Execute(query2);
    返回Enumerable.Concat(results1,results2);
    }
    私有静态表达式ChangeQuerySource(表达式查询、表达式新闻源)
    {
    //步骤1:将表达式强制转换为MethodCallExpression。
    //这通常是可行的,因为一系列LINQ语句
    //通常是一个方法调用链,但我不会
    //在生产代码中做这样一个盲目的假设。
    var methodCallExpression=(methodCallExpression)查询;
    //步骤2:创建一个新的MethodCallExpression,传入
    //现有一个的MethodInfo,所以我们调用相同的
    //方法,但只是更改参数。还记得LINQ吗
    //方法是扩展方法,因此第一个参数是
    //始终是来源。我们保留任何其他论点。
    query=Expression.Call(
    methodCallExpression.Method,
    新表达式[]{newSource}.Concat(methodCallExpression.Arguments.Skip(1));
    //步骤3:我们在末尾调用.AsEnumerable(),以获得一个
    //IEnumerable的最终返回类型,而不是
    //IQueryable,所以我们可以安全地使用这个新表达式
    //任何IEnumerable语句中的树。
    query=Expression.Call(
    typeof(Enumerable).GetMethod(“AsEnumerable”,BindingFlags.Static | BindingFlags.Public)
    .MakeGenericMethod(
    TypeSystem.GetElementType(methodCallExpression.Arguments[0].Type)
    ),
    查询);
    返回查询;
    }
    
    听起来,如果我理解正确,您可能需要使用一些抽象类。如果是这样,请阅读本文。谢谢您的回复。你对我应该使用哪些课程有什么建议吗?我之所以假设我需要实现IQueryable,是因为我正在使用的接口需要这样的接口,而我不知道有任何抽象(或具体的)类可以提供我所需的连接属性