实现带有内存查询的自定义QueryProvider



我试图在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的参数之一是我的自定义查询。当提供程序执行这个表达式时,它会导致CreateQueryExecute之间的无限调用循环。是否可以将所有引用更改为我的自定义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();

正如我所说,没有测试复杂的场景,但我想只有一些调整对于现实世界的使用是必要的

最新更新