Custom QueryProvider implementation with in-memory query

I am trying to create a wrapper around QueryableBase and INhQueryProvider that will receive a collection in the constructor and query it in memory instead of going to the database. Therefore, I can make fun of the behavior of NHibernate ToFuture() and correctly unit test my classes.

The problem is that I ran into a stack overflow due to infinite recursion, and I try my best to find the reason.

Here is my implementation:

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

Edit: I somehow understood the problem. One of the arguments to expression is my user request. When this expression is executed by the provider, it invokes an infinite call loop between CreateQuery and Execute . Is it possible to change all links to my custom query request wrapped in this class?

+7
c # linq nhibernate
source share
1 answer

After a while, I decided to try again, and I probably managed to mock it. I have not tested it with real case scenarios, but I do not think that many settings will be needed. Most of this code is either taken from this tutorial . There are some caveats related to IEnumerable when dealing with these queries.

We need to implement QueryableBase , since NHibernate claims 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) { } } 

Now we need to make fun of a QueryProvider , since those LINQ queries depend on them, 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 visitor of the expression tree will change the type of request 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 an assistant (taken from the tutorial) to get the requested type:

 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 check the code above, I executed 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, complex scenarios have not been tested, but I think that for real purposes only a few settings will be required.

+3
source share

All Articles