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) {
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.