Как обернуть Linq2NHibernate.Fetch и .ThenFetch внутри моего абстрактного репозитория?

Я использую общий репозиторий, который предоставляет 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, что вызывает две проблемы:

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

  • Когда я пытаюсь выполнить unit test, методы .Fetch и .ThenFetch выходят из строя при выполнении IQueryable<T>, которые предоставляет мой mock-репозиторий.

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

Update:

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

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.

Ответы

Ответ 1

После некоторого расследования я думаю, что у меня есть рецепт: просто следуйте за реализацией 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);
    }
}

Ответ 2

Основываясь на ответе guido, здесь, который отключает все зависимости 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
}

Ответ 3

Что я сделал для этого, так это создать функцию public virtual в моем репозитории для EagerlyFetch моего объекта. Затем в моих модульных тестах я использую этот Stub вместо этого, который проходит через все, кроме моего метода 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();
    }
}

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

Приветствия,

Леви

Ответ 4

Альтернативно оберните ваши тестовые данные IEnumerable в заглушку, которая реализует IFutureValue (NB это использует remotion 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; }
    }
}

(Я не писал это, кредит коллеге гораздо более квалифицированный, чем я)