如何在我的抽象存储库中包装Linq2NHibernate的.Fetch和.ThenFetch?

时间:2011-01-26 20:58:32

标签: repository linq-to-nhibernate eager-loading

我正在使用一个公开的存储库,它会像这样公开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>执行时失败。

  3. 如何将这些包装在我的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 个答案:

答案 0 :(得分:13)

经过一些调查后,我认为我有一个方法:只需密切关注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 :(得分:1)

基于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
}

答案 2 :(得分:0)

我为解决这个问题所做的工作是在我的存储库中创建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();
    }
}

这可以替代上面的一些答案(我还没试过),但这对我有用。

干杯,

列维

答案 3 :(得分:0)

或者将测试数据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; }
    }
}

(我没有写这个,归功于比我更熟练的同事)