Как мне обернуть Linq2NHibernate's .Fetch и .ThenFetch внутри моего абстрактного репозитория? - PullRequest
14 голосов
/ 26 января 2011

Я использую общий репозиторий, который предоставляет IQueryable<T>, например:

public IQueryable<T> AllEntities
{
    get
    {
        return session.Query<T>();
    }
}

Я могу запросить это так:

var results =
    (from e in repository.AllEntities
     where e.SomeProperty == "some value"
     select e).ToList();

Однако, если T имеет родительский и родительский объект, и я хочу загрузить их с нетерпением, я должен сделать это:

var results =
    (from e in repository.AllEntities
     where e.SomeProperty == "some value"
     select e)
    .Fetch(x => x.Parent)
    .ThenFetch(x => x.Grandparent)
    .ToList();

Это работает, но .Fetch и .ThenFetch оба являются специфическими методами расширения Linq2Nhibernate, что вызывает две проблемы:

  1. Я должен добавить оператор using NHibernate.Linq; вверху моего файла. Однако в тот момент, когда я делаю этот запрос, он должен быть независим от реализации.

  2. Когда я пытаюсь выполнить модульное тестирование, методы .Fetch и .ThenFetch завершаются с ошибкой при выполнении с IQueryable<T>, предоставленным моим репозиторием.

Как мне обернуть их внутри моего IRepository<T> интерфейса или внутри некоторых общих методов расширения?

Обновление:

Пока все, что я придумал, это добавить это в мой интерфейс репозитория:

IQueryable<T> EagerLoadParent<U>(IQueryable<T> query, 
    Expression<Func<T, U>> parentExpression);
IQueryable<T> EagerLoadParent<U, V>(IQueryable<T> query,
    Expression<Func<T, U>> parentExpression, 
    Expression<Func<U, V>> grandparentExpression);

... и это в моей реализации репозитория NHibernate:

public IQueryable<T> EagerLoadParent<U>(IQueryable<T> query,
    Expression<Func<T, U>> parentExpression)
{
    return query
        .Fetch(parentExpression);
}

public IQueryable<T> EagerLoadParent<U, V>(IQueryable<T> query,
    Expression<Func<T, U>> parentExpression, 
    Expression<Func<U, V>> grandparentExpression)
{
    return query
        .Fetch(parentExpression)
        .ThenFetch(grandparentExpression);
}

Потребитель этого API теперь делает это:

var query =
    (from e in repository.AllEntities
     where e.SomeProperty == "some value"
     select e);
var results = repository
    .EagerLoadParent(query, e => e.Parent, p => p.Grandparent)
    .ToList();

Но в нем отсутствует красивый синтаксис метода расширения, который я бы предпочел. Я ищу что-то ближе к синтаксису .Fetch и .ThenFetch.

Ответы [ 4 ]

13 голосов
/ 21 апреля 2011

После некоторого исследования я думаю, что у меня есть рецепт: просто внимательно следите за реализацией NHibernate.Linq, чтобы иметь собственную реализацию и избежать явной зависимости NHibernate.Linq в вашем клиентском коде.Вам просто нужно очень близко воспроизвести NHibernate.Linq.EagerFetchingExtensionMethods класс.

Требуется интерфейс: IFetchRequest, класс FetchRequest, реализующий IFetchRequest, и статический класс EagerFetch, реализующий методы расширения.Это своего рода клон класса NHibernate.Linq.EagerFetchingExtensionMethods.

Просто определите:

public interface IFetchRequest<TQueried, TFetch> : IOrderedQueryable<TQueried> {}

, который имитирует NHibernate.Linq.INhFetchRequest<TQueried, TFetch>

, затем определите реализацию:

public class FetchRequest<TQueried, TFetch> : IFetchRequest<TQueried, TFetch> {

    #region IEnumerable<TQueried> Members

    public IEnumerator<TQueried> GetEnumerator(){
        return NhFetchRequest.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return NhFetchRequest.GetEnumerator();
    }

    #endregion

    #region IQueryable Members

    public Type ElementType {
        get { return NhFetchRequest.ElementType; }
    }

    public System.Linq.Expressions.Expression Expression {
        get { return NhFetchRequest.Expression; }
    }

    public IQueryProvider Provider {
        get { return NhFetchRequest.Provider; }
    }

    #endregion

    public FetchRequest(INhFetchRequest<TQueried, TFetch> nhFetchRequest){
        NhFetchRequest = nhFetchRequest;
    }

    public INhFetchRequest<TQueried, TFetch> NhFetchRequest { get; private set; }
}

Этот просто содержит реализацию nHibernate и перенаправляет каждый метод этому члену.

Наконец:

public static class EagerFetch {
/*
    replacing methods from NHibernate.Linq.EagerFetchingExtensionMethods
    private static INhFetchRequest<TOriginating, TRelated> CreateFluentFetchRequest<TOriginating, TRelated>(MethodInfo currentFetchMethod, IQueryable<TOriginating> query, LambdaExpression relatedObjectSelector);
    public static INhFetchRequest<TOriginating, TRelated> Fetch<TOriginating, TRelated>(this IQueryable<TOriginating> query, Expression<Func<TOriginating, TRelated>> relatedObjectSelector);
    public static INhFetchRequest<TOriginating, TRelated> FetchMany<TOriginating, TRelated>(this IQueryable<TOriginating> query, Expression<Func<TOriginating, IEnumerable<TRelated>>> relatedObjectSelector);
    public static INhFetchRequest<TQueried, TRelated> ThenFetch<TQueried, TFetch, TRelated>(this INhFetchRequest<TQueried, TFetch> query, Expression<Func<TFetch, TRelated>> relatedObjectSelector);
    public static INhFetchRequest<TQueried, TRelated> ThenFetchMany<TQueried, TFetch, TRelated>(this INhFetchRequest<TQueried, TFetch> query, Expression<Func<TFetch, IEnumerable<TRelated>>> relatedObjectSelector);
*/  
    public static IFetchRequest<TOriginating, TRelated> Fetch<TOriginating, TRelated>(this IQueryable<TOriginating> query, Expression<Func<TOriginating, TRelated>> relatedObjectSelector){
        var fetch = EagerFetchingExtensionMethods.Fetch(query, relatedObjectSelector);
        return new FetchRequest<TOriginating, TRelated>(fetch);
    }

    public static IFetchRequest<TOriginating, TRelated> FetchMany<TOriginating, TRelated>(this IQueryable<TOriginating> query, Expression<Func<TOriginating, IEnumerable<TRelated>>> relatedObjectSelector){
        var fecth = EagerFetchingExtensionMethods.FetchMany(query, relatedObjectSelector);
        return new FetchRequest<TOriginating, TRelated>(fecth);
    }

    public static IFetchRequest<TQueried, TRelated> ThenFetch<TQueried, TFetch, TRelated>(this IFetchRequest<TQueried, TFetch> query, Expression<Func<TFetch, TRelated>> relatedObjectSelector){
        var impl = query as FetchRequest<TQueried, TFetch>;
        var fetch = EagerFetchingExtensionMethods.ThenFetch(impl.NhFetchRequest, relatedObjectSelector);
        return new FetchRequest<TQueried, TRelated>(fetch);
    }

    public static IFetchRequest<TQueried, TRelated> ThenFetchMany<TQueried, TFetch, TRelated>(this IFetchRequest<TQueried, TFetch> query, Expression<Func<TFetch, IEnumerable<TRelated>>> relatedObjectSelector){
        var impl = query as FetchRequest<TQueried, TFetch>;
        var fetch = EagerFetchingExtensionMethods.ThenFetchMany(impl.NhFetchRequest, relatedObjectSelector);
        return new FetchRequest<TQueried, TRelated>(fetch);
    }
}
1 голос
/ 22 августа 2011

Основываясь на ответе Гвидо, вот тот, который отключает все зависимости NHibernate от интерфейса репозитория.Впрочем, совсем немного, и, вероятно, не очень хорошая техника, если вы хотите использовать множество специфических функций NHibernate;тогда ссылка на NHibernate.dll может быть более подходящей.

Сначала интерфейсы:

public interface IFetchableQueryable<TQueried> : IQueryable<TQueried> {
        IFetchRequest<TQueried, TRelated> Fetch<TRelated>(Expression<Func<TQueried, TRelated>> relatedObjectSelector);

        IFetchRequest<TQueried, TRelated> FetchMany<TRelated>(Expression<Func<TQueried, IEnumerable<TRelated>>> relatedObjectSelector);
}

public interface IFetchRequest<TQueried, TFetch> : IOrderedQueryable<TQueried> {
        IFetchRequest<TQueried, TRelated> ThenFetch<TRelated>(Expression<Func<TFetch, TRelated>> relatedObjectSelector);

        IFetchRequest<TQueried, TRelated> ThenFetchMany<TRelated>(Expression<Func<TFetch, IEnumerable<TRelated>>> relatedObjectSelector);
}

А затем реализация:

    public class FetchableQueryable<TQueried> : IFetchableQueryable<TQueried> {
    public FetchableQueryable(IQueryable<TQueried> query) {
        this.Query = query;
    }

    public IQueryable<TQueried> Query { get; private set; }

    #region IEnumerable<TQueried> Members

    public IEnumerator<TQueried> GetEnumerator() {
        return this.Query.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        return this.Query.GetEnumerator();
    }

    #endregion

    #region IQueryable Members

    public Type ElementType {
        get { return this.Query.ElementType; }
    }

    public Expression Expression {
        get { return this.Query.Expression; }
    }

    public IQueryProvider Provider {
        get { return this.Query.Provider; }
    }

    #endregion

    #region IFetchableQueryable<TQueried> Members

    public IFetchRequest<TQueried, TRelated> Fetch<TRelated>(Expression<Func<TQueried, TRelated>> relatedObjectSelector) {
        return new FetchRequest<TQueried, TRelated>(this.Query.Fetch<TQueried, TRelated>(relatedObjectSelector));
    }

    public IFetchRequest<TQueried, TRelated> FetchMany<TRelated>(Expression<Func<TQueried, IEnumerable<TRelated>>> relatedObjectSelector) {
        return new FetchRequest<TQueried, TRelated>(this.Query.FetchMany<TQueried, TRelated>(relatedObjectSelector));
    }

    #endregion
}

public class FetchRequest<TQueried, TFetch> : IFetchRequest<TQueried, TFetch> {

    public FetchRequest(INhFetchRequest<TQueried, TFetch> nhFetchRequest) {
        NhFetchRequest = nhFetchRequest;
    }

    public INhFetchRequest<TQueried, TFetch> NhFetchRequest { get; private set; }

    #region IEnumerable<TQueried> Members

    public IEnumerator<TQueried> GetEnumerator() {
        return NhFetchRequest.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        return NhFetchRequest.GetEnumerator();
    }

    #endregion

    #region IQueryable Members

    public Type ElementType {
        get { return NhFetchRequest.ElementType; }
    }

    public System.Linq.Expressions.Expression Expression {
        get { return NhFetchRequest.Expression; }
    }

    public IQueryProvider Provider {
        get { return NhFetchRequest.Provider; }
    }

    #endregion

    #region IFetchRequest<TQueried,TFetch> Members

    public IFetchRequest<TQueried, TRelated> Fetch<TRelated>(Expression<Func<TQueried, TRelated>> relatedObjectSelector) {
        var fetch = EagerFetchingExtensionMethods.Fetch(this.NhFetchRequest, relatedObjectSelector);
        return new FetchRequest<TQueried, TRelated>(fetch);
    }

    public IFetchRequest<TQueried, TRelated> FetchMany<TRelated>(Expression<Func<TQueried, IEnumerable<TRelated>>> relatedObjectSelector) {
        var fecth = EagerFetchingExtensionMethods.FetchMany(this.NhFetchRequest, relatedObjectSelector);
        return new FetchRequest<TQueried, TRelated>(fecth);
    }

    public IFetchRequest<TQueried, TRelated> ThenFetch<TRelated>(Expression<Func<TFetch, TRelated>> relatedObjectSelector) {
        var fetch = EagerFetchingExtensionMethods.ThenFetch(this.NhFetchRequest, relatedObjectSelector);
        return new FetchRequest<TQueried, TRelated>(fetch);
    }

    public IFetchRequest<TQueried, TRelated> ThenFetchMany<TRelated>(Expression<Func<TFetch, IEnumerable<TRelated>>> relatedObjectSelector) {
        var fetch = EagerFetchingExtensionMethods.ThenFetchMany(this.NhFetchRequest, relatedObjectSelector);
        return new FetchRequest<TQueried, TRelated>(fetch);
    }

    #endregion
}
0 голосов
/ 07 августа 2014

В качестве альтернативы оберните ваши тестовые данные IEnumerable в заглушку, которая реализует IFutureValue (примечание: для этого используется удаление http://relinq.codeplex.com/).

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using NHibernate;
using Remotion.Linq;

namespace SomeNameSpaceNearYou
{
    public class NhStubQueryable<TData> : QueryableBase<TData>, IEnumerable<TData>, IFutureValue<TData>
    {
        private readonly IEnumerable<TData> _enumerable;

        public NhStubQueryable(IEnumerable<TData> enumerable)
            : base(new NhStubQueryProvider())
        {
            _enumerable = enumerable;
        }

        /// <summary>
        /// This constructor is called by Provider.CreateQuery().
        /// </summary>
        //public NhStubQueryable(NhStubQueryProvider<TData> provider, Expression expression)
        public NhStubQueryable(NhStubQueryProvider provider, Expression expression)
            : base(provider, expression)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }

            if (!typeof(IQueryable<TData>).IsAssignableFrom(expression.Type))
            {
                throw new ArgumentOutOfRangeException("expression");
            }
        }
        #endregion

        #region Enumerators
        IEnumerator<TData> IEnumerable<TData>.GetEnumerator()
        {
            if (_enumerable != null)
                return _enumerable.GetEnumerator();
            return (Provider.Execute<IEnumerable<TData>>(Expression)).GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            if (_enumerable != null)
                return _enumerable.GetEnumerator();
            return (Provider.Execute<IEnumerable<TData>>(Expression)).GetEnumerator();
        }
        public new IEnumerator<TData> GetEnumerator()
        {
            if (_enumerable != null)
                return _enumerable.GetEnumerator();
            return (Provider.Execute<IEnumerable<TData>>(Expression)).GetEnumerator();
        }


        #endregion
        public IEnumerable Enumerable { get { return _enumerable; } }

        public TData Value { get { return this.FirstOrDefault(); } }
    }

    public class NhStubFutureValue<TData> :  IFutureValue<TData>
    {
        public NhStubFutureValue(TData value)
        {
            Value = value;
        }

        public TData Value { get; private set; }
    }
}

(я не писал этого, спасибо коллеге, намного более опытному, чем я)

0 голосов
/ 01 ноября 2011

Чтобы обойти это, я создал функцию public virtual в моем репозитории для EagerlyFetch моего объекта. Затем в своих модульных тестах я использую эту заглушку, которая проходит через все, кроме моего метода EagerlyFetch, который просто возвращает список. Вот пример того, что я сделал:

public class PersistenceBroker
{
    private ISession _session;

    public IQueryable<T> Query<T>()
    {
        return Session.Query<T>();
    }
    .
    .
    .
}

public class PersonRepository : IPersonRepository
{
    private PersistenceBroker _persistenceBroker;

    public List<Person> PeopeWhoLiveIn(string city)
    {
        var people = _persistenceBroker.Query<Person>()
            Where(x => x.City == city)l

        return EagerlyFetch(people);
    }

    public virtual List<Person> EagerlyFetch(IQueryable<Person> people)
    {
        return people.Fetch(x => x.Mom)
            .FetchMany(x => x.Children)
            .ToList();
    }
}

А затем в своих тестах я просто предоставляю PersonRepositoryStub:

public class PersonRepositoryStub : PersonRepository
{
    public override List<Person> EagerlyFetch(IQueryable<Person> people)
    {
        return people.ToList();
    }
}

Это будет альтернативой некоторым из приведенных выше ответов (которые я не пробовал), но это работает для меня.

Приветствия

Леви

...