I have searched the internet for the last couple days for a solution to this, and haven't found what I've wanted. Basically, here is my problem:
- I have an interface I need to implement that has a method that returns an IQueryable (I don't have access to the interface, so I cannot change this)
- I would like the method to return the concatenation of (a) an IQueryable that points to a very large database table, and (b) a large IEnumerable that has been computed in memory of the same Entity type
- I cannot do queryableA.Concat(enumerableB).Where(condition) because it will try to send the entire array to the server (and, aside from that, I get an exception that it only supports primitive types)
- I cannot do enumerableB.Concat(queryableA).Where(condition) because it will pull the entirety of the table into memory and treat it as an IEnumerable
So, after some searching, I think I've decided a good way to approach this problem is to write my own ConcatenatingQueryable implementation of IQueryable that takes two IQueryable's and executes the Expression tree on each independently, and then concatenations the results. However, I seem to be having issues as it returns a stack overflow. Based on http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx, this is what I've implemented so far:
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;
}
}
}
I don't have much experience with this interface, and am a bit lost as to what to do from here. Does anyone have any suggestions on how to do this? I'm also open to abandoning this approach entirely if need be.
Just to reiterate, I'm getting a StackOverflowException, and the stacktrace is simply a bunch of calls between the two commented lines above, with "[External Code]" in between each pair of calls. I have added an example Main method that uses two tiny enumerables, but you can imagine these were larger data sources that take a very long time to enumerate.
Thank you very much in advance for your help!