使用内存中查询实现自定义QueryProvider

时间:2016-09-05 22:21:35

标签: c# linq nhibernate

我试图在QueryableBaseINhQueryProvider周围创建一个包装器,它将在构造函数中接收一个集合并在内存中查询它而不是转到数据库。这样我就可以嘲笑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之间的无限调用循环。是否可以将我的自定义查询的所有引用更改为此类包装的可查询对象?

1 个答案:

答案 0 :(得分:4)

过了一会儿,我决定再试一次,我想我已经设法嘲笑它了。我没有用真实场景测试它,但我不认为有必要进行许多调整。此代码的大部分内容取自this tutorial或基于asserts the type。在处理这些查询时,有一些与IEnumerable相关的警告。

我们需要在使用QueryableBase时自NHibernate uses it实施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()也one?

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();

正如我所说,没有测试过复杂的场景,但我想只需要进行一些调整就可以实现真实用法。