gpt4 book ai didi

c# - 将 IQueryable 与 IEnumerable 连接成 IQueryable

转载 作者:行者123 更新时间:2023-11-30 12:27:14 24 4
gpt4 key购买 nike

最近几天我在互联网上搜索了解决方案,但没有找到我想要的。基本上,这是我的问题:

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

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

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 方法,它使用两个微小的枚举,但您可以想象这些是需要很长时间才能枚举的更大的数据源。

非常感谢您的帮助!

最佳答案

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

如果我们从逻辑上遵循这一点,这意味着链中的第一个 LINQ 方法必须有一个源参数,从代码中可以清楚地看出它的源实际上是相同的 IQueryable这首先启动了整个事情(您的 ConcatenatingQueryable)。

当您构建这个时,您的想法几乎是正确的 - 您只需要更进一步。我们需要做的是重新指向第一个 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;
}

关于c# - 将 IQueryable 与 IEnumerable 连接成 IQueryable,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25855152/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com