实现带有内存查询的自定义QueryProvider
本文关键字:自定义 QueryProvider 查询 内存 实现 | 更新日期: 2023-09-27 18:05:58
我试图在QueryableBase和INhQueryProvider周围创建一个包装器,它将在构造函数中接收一个集合并在内存中查询它,而不是去数据库。这样我就可以模拟NHibernate的ToFuture()
的行为,并正确地对我的类进行单元测试。
问题是,由于无限递归,我正面临堆栈溢出,我正在努力寻找原因。
这是我的实现:
public class NHibernateQueryableProxy<T> : QueryableBase<T>, IOrderedQueryable<T>
{
public NHibernateQueryableProxy(IQueryable<T> data) : base(new NhQueryProviderProxy<T>(data))
{
}
public NHibernateQueryableProxy(IQueryParser queryParser, IQueryExecutor executor) : base(queryParser, executor)
{
}
public NHibernateQueryableProxy(IQueryProvider provider) : base(provider)
{
}
public NHibernateQueryableProxy(IQueryProvider provider, Expression expression) : base(provider, expression)
{
}
public new IEnumerator<T> GetEnumerator()
{
return Provider.Execute<IEnumerable<T>>(Expression).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
internal class NhQueryProviderProxy<T> : INhQueryProvider
{
private readonly IQueryProvider provider;
public NhQueryProviderProxy(IQueryable<T> data)
{
provider = data.AsQueryable().Provider;
}
public IQueryable CreateQuery(Expression expression)
{
return new NHibernateQueryableProxy<T>(this, expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new NHibernateQueryableProxy<TElement>(this, expression);
}
public object Execute(Expression expression)
{
return provider.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return provider.Execute<TResult>(expression);
}
public object ExecuteFuture(Expression expression)
{
return provider.Execute(expression);
}
public void SetResultTransformerAndAdditionalCriteria(IQuery query, NhLinqExpression nhExpression, IDictionary<string, Tuple<object, IType>> parameters)
{
throw new NotImplementedException();
}
}
编辑:我有点找到问题了。expression
的参数之一是我的自定义查询。当提供程序执行这个表达式时,它会导致CreateQuery
和Execute
之间的无限调用循环。是否可以将所有引用更改为我的自定义queryable到这个类包装的queryable ?
过了一会儿,我决定再试一次,我想我已经成功地嘲笑了它。我没有在真实情况下测试它,但我认为没有必要做太多调整。大部分代码取自本教程或基于本教程。在处理这些查询时,有一些与IEnumerable
相关的注意事项。
我们需要实现QueryableBase
,因为NHibernate在使用ToFuture
时断言类型。
public class NHibernateQueryableProxy<T> : QueryableBase<T>
{
public NHibernateQueryableProxy(IQueryable<T> data) : base(new NhQueryProviderProxy<T>(data))
{
}
public NHibernateQueryableProxy(IQueryProvider provider, Expression expression) : base(provider, expression)
{
}
}
现在我们需要模拟QueryProvider
,因为这是LINQ查询所依赖的,它需要实现INhQueryProvider
,因为ToFuture()也使用它。
public class NhQueryProviderProxy<T> : INhQueryProvider
{
private readonly IQueryable<T> _data;
public NhQueryProviderProxy(IQueryable<T> data)
{
_data = data;
}
// These two CreateQuery methods get called by LINQ extension methods to build up the query
// and by ToFuture to return a queried collection and allow us to apply more filters
public IQueryable CreateQuery(Expression expression)
{
Type elementType = TypeSystem.GetElementType(expression.Type);
return (IQueryable)Activator.CreateInstance(typeof(NHibernateQueryableProxy<>)
.MakeGenericType(elementType), new object[] { this, expression });
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new NHibernateQueryableProxy<TElement>(this, expression);
}
// Those two Execute methods are called by terminal methods like .ToList() and .ToArray()
public object Execute(Expression expression)
{
return ExecuteInMemoryQuery(expression, false);
}
public TResult Execute<TResult>(Expression expression)
{
bool IsEnumerable = typeof(TResult).Name == "IEnumerable`1";
return (TResult)ExecuteInMemoryQuery(expression, IsEnumerable);
}
public object ExecuteFuture(Expression expression)
{
// Here we need to return a NhQueryProviderProxy so we can add more queries
// to the queryable and use another ToFuture if desired
return CreateQuery(expression);
}
private object ExecuteInMemoryQuery(Expression expression, bool isEnumerable)
{
var newExpr = new ExpressionTreeModifier<T>(_data).Visit(expression);
if (isEnumerable)
{
return _data.Provider.CreateQuery(newExpr);
}
return _data.Provider.Execute(newExpr);
}
public void SetResultTransformerAndAdditionalCriteria(IQuery query, NhLinqExpression nhExpression, IDictionary<string, Tuple<object, IType>> parameters)
{
throw new NotImplementedException();
}
}
表达式树访问器将为我们更改查询的类型:
internal class ExpressionTreeModifier<T> : ExpressionVisitor
{
private IQueryable<T> _queryableData;
internal ExpressionTreeModifier(IQueryable<T> queryableData)
{
_queryableData = queryableData;
}
protected override Expression VisitConstant(ConstantExpression c)
{
// Here the magic happens: the expression types are all NHibernateQueryableProxy,
// so we replace them by the correct ones
if (c.Type == typeof(NHibernateQueryableProxy<T>))
return Expression.Constant(_queryableData);
else
return c;
}
}
我们还需要一个帮助器(取自教程)来获取查询的类型:
internal static class TypeSystem
{
internal static Type GetElementType(Type seqType)
{
Type 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 (Type arg in seqType.GetGenericArguments())
{
Type ienum = typeof(IEnumerable<>).MakeGenericType(arg);
if (ienum.IsAssignableFrom(seqType))
{
return ienum;
}
}
}
Type[] ifaces = seqType.GetInterfaces();
if (ifaces != null && ifaces.Length > 0)
{
foreach (Type iface in ifaces)
{
Type ienum = FindIEnumerable(iface);
if (ienum != null) return ienum;
}
}
if (seqType.BaseType != null && seqType.BaseType != typeof(object))
{
return FindIEnumerable(seqType.BaseType);
}
return null;
}
}
为了测试上面的代码,我运行了以下代码片段:
var arr = new NHibernateQueryableProxy<int>(Enumerable.Range(1, 10000).AsQueryable());
var fluentQuery = arr.Where(x => x > 1 && x < 4321443)
.Take(1000)
.Skip(3)
.Union(new[] { 4235, 24543, 52 })
.GroupBy(x => x.ToString().Length)
.ToFuture()
.ToList();
var linqQuery = (from n in arr
where n > 40 && n < 50
select n.ToString())
.ToFuture()
.ToList();
正如我所说,没有测试复杂的场景,但我想只有一些调整对于现实世界的使用是必要的