2016-09-05 72 views
7

Próbuję utworzyć opakowanie o numerach QueryableBase i INhQueryProvider, które będą odbierać kolekcję w konstruktorze i wywoływać ją w pamięci zamiast przechodzić do bazy danych. To jest tak, że mogę wyśmiać zachowanie NHibernate's ToFuture() i poprawnie przetestować moje klasy.Implementowanie niestandardowego zapytania QueryProvider z zapytaniem w pamięci

Problem polega na tym, że mam do czynienia z przepełnieniem stosu z powodu nieskończonej rekurencji i staram się znaleźć przyczynę.

Oto moja realizacja:

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: mam rodzaj zorientowali się problem. Jeden z argumentów do expression jest moim niestandardowym zapytaniem. Gdy to wyrażenie jest wykonywane przez dostawcę, wywołuje nieskończoną pętlę wywołania między CreateQuery i Execute. Czy można zmienić wszystkie odwołania do moich niestandardowych zapytań do kwerendy opakowanej przez tę klasę?

+0

Czy kiedykolwiek to rozgryzłeś? –

+0

Nie, w końcu stworzyłem własne 'ToFuture', które nazwał NH' ToFuture' w kodzie produkcyjnym i 'ToList' w kodzie testowym. Ale domyślam się, że musiałbyś napisać gościa do wyrażenia, aby ręcznie wymienić parametry. To była moja następna próba. –

+0

@CallumBradbury, jeśli nadal potrzebujesz, myślę, że udało mi się z niego kpić. Sprawdź odpowiedź. –

Odpowiedz

3

Po jakimś czasie postanowiłem spróbować jeszcze raz i chyba udało mi się to udawać. Nie testowałem go w rzeczywistych przypadkach, ale nie sądzę, że konieczne będzie wprowadzenie wielu poprawek. Większość tego kodu jest pobierana lub oparta na this tutorial. Podczas przetwarzania tych zapytań istnieją pewne zastrzeżenia związane z IEnumerable.

Musimy wdrożyć QueryableBase od NHibernate asserts the type podczas korzystania z 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) 
    { 
    } 
} 

Teraz musimy wyśmiewać się QueryProvider ponieważ to właśnie zapytań LINQ zależą i musi wdrożyć INhQueryProvider ponieważ ToFuture() również 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(); 
    } 
} 

Wyrażenie drzewo gościem będzie zmienić rodzaj zapytania do nas:

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

I my również potrzebujemy pomocnika (wzięte z samouczka), aby uzyskać typ czym zapytał:

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

Aby przetestować powyższy kod, wpadłem na następujący fragment:

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

Jak już powiedziałem, nie testowano żadnych skomplikowanych scenariuszy, ale wydaje mi się, że tylko kilka poprawek będzie konieczne do zastosowań w świecie rzeczywistym.

+0

Wow, świetna robota! Spróbuję wdrożyć to w niektórych moich materiałach, gdy następnym razem dostanę szansę –

+0

Mam szansę wrócić i spróbować wdrożyć to i działa świetnie! Miałem pewne problemy z ToFutureValue i musiałem napisać niestandardową implementację IFutureValue , aby sobie z tym poradzić, ale poza tym działa jak urok :) –

+0

@CallumBradbury dzięki za poinformowanie mnie! Jeśli chcesz, mogę rozszerzyć rozwiązanie o uwzględnioną implementację. –

Powiązane problemy