Concatenating an IQueryable with an IEnumerable into an IQueryable
最近几天,我在网上搜索了一个解决方案,但还没有找到我想要的。基本上,我的问题是:
因此,在进行了一些搜索之后,我认为我已经决定了一个解决这个问题的好方法,那就是编写我自己的IQueryable的可连接查询实现,它接受两个IQueryable,并独立地对每个IQueryable执行表达式树,然后连接结果。但是,当它返回堆栈溢出时,我似乎遇到了一些问题。基于http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx,这是我迄今为止实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | 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,stackTrace只是上面两行注释之间的一系列调用,每对调用之间都有"[外部代码]"。我添加了一个使用两个小的可枚举项的主方法示例,但是您可以想象这些是需要很长时间才能枚举的较大的数据源。
非常感谢您的帮助!
当您分解传递到
如果从逻辑上讲,这意味着链中的第一个linq方法必须有一个源参数,而且从代码中可以清楚地看到,它的源实际上与启动整个过程的
当你建造这个的时候,你的想法是正确的-你只需要再往前走一小步。我们需要做的是重新指出,第一个LINQ方法使用实际的源,然后允许执行遵循其自然路径。
下面是一些执行此操作的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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; } |