使用内存中查询实现自定义QueryProvider [英] Implementing a custom QueryProvider with in-memory query

查看:39
本文介绍了使用内存中查询实现自定义QueryProvider的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试围绕 QueryableBase INhQueryProvider ,它将在构造函数中接收集合并对其进行查询在内存中,而不是去数据库.这样一来,我就可以模拟NHibernate的 ToFuture的行为() 并正确地对我的课程进行单元测试.

I'm trying to create a wrapper around QueryableBase and INhQueryProvider that would receive a collection in the constructor and query it in-memory instead of going to a database. This is so I can mock the behavior of NHibernate's ToFuture() and properly unit test my classes.

问题是由于无限递归,我面临着堆栈溢出的问题,而我一直在努力寻找原因.

The problem is that I'm facing a stack overflow due to infinite recursion and I'm struggling to find the reason.

这是我的实现方式

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 之间的无限调用循环.是否可以将对我的自定义可查询对象的所有引用更改为此类包装的可查询对象?

I've kind of figured out the problem. One of the arguments to expression is my custom queryable. When this expression is executed by the provider, it causes an infinite call loop between CreateQuery and Execute. Is it possible to change all the references to my custom queryable to the queryable wrapped by this class?

推荐答案

一段时间后,我决定再次尝试一下,但我想我已经设法对其进行了模拟.我没有在实际案例中进行测试,但是我认为没有必要进行许多调整.此代码大部分来自或基于本教程.处理这些查询时,有一些与 IEnumerable 相关的警告.

After a while I decided to give it another try and I guess I've managed to mock it. I didn't test it with real case scenarios but I don't think many tweaks will be necessary. Most of this code is either taken from or based on this tutorial. There are some caveats related to IEnumerable when dealing with those queries.

我们需要实现 QueryableBase ,因为NHibernate

We need to implement QueryableBase since NHibernate asserts the type when using 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() 也

Now we need to mock a QueryProvider since that's what LINQ queries depend on and it needs to implement INhQueryProvider because ToFuture() also uses it.

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

表达式树访问者将为我们更改查询的类型:

The expression tree visitor will change the type of the query for us:

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;
    }
}

我们还需要一个帮助程序(来自本教程)来获取要查询的类型:

And we also need a helper (taken from the tutorial) to get the type being queried:

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;
    }
}

要测试以上代码,我运行了以下代码段:

To test the above code, I ran the following snippet:

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

正如我所说,没有测试过复杂的场景,但我想对于实际使用情况,只需进行一些调整即可.

As I said, no complex scenarios were tested but I guess only a few tweaks will be necessary for real-world usages.

这篇关于使用内存中查询实现自定义QueryProvider的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆