开发者

How do I wrap Linq2NHibernate's .Fetch and .ThenFetch inside my abstract repository?

开发者 https://www.devze.com 2023-02-07 02:37 出处:网络
I\'m using a generic repository that exposes an IQueryable<T> like this: public IQueryable<T> AllEntities

I'm using a generic repository that exposes an IQueryable<T> like this:

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

I can query like this:

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

However, if T has a parent and grandparent entity and I want to load them eagerly, I have to do this:

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

This works, but .Fetch and .ThenFetch are both Linq2Nhibernate specific extension methods, which is causing two problems:

  1. I have to include a using NHibernate.Linq; statement at the top of my file. However, at the p开发者_Go百科oint that I'm doing this query, it should be implementation agnostic.

  2. When I try to unit test this, the .Fetch and .ThenFetch methods fail when executed against the IQueryable<T> that my mock repository provides.

How can I wrap these inside of my IRepository<T> interface, or inside of some generic extension methods?

Update:

So far all I've come up with is to add this to my repository interface:

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

... and this to my NHibernate repository implementation:

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

The consumer of this API now does this:

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

But this lacks the nice extension method syntax I'd prefer. I'm looking for something closer to the .Fetch and .ThenFetch syntax.


After some investigation I think I have a recipe: simply follow closely NHibernate.Linq implementation in order to have your own implementation and avoid an explicit NHibernate.Linq dependency in your client code. You just need to reproduce very closely NHibernate.Linq.EagerFetchingExtensionMethods class.

It takes an interface: IFetchRequest, a class FetchRequest implementing IFetchRequest and a static class EagerFetch implementing extension methods. This is a sort of clone of NHibernate.Linq.EagerFetchingExtensionMethods class.

Just define:

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

which mimics NHibernate.Linq.INhFetchRequest<TQueried, TFetch>

then define an implementation:

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

This one simply holds a nHibernate implementation and forwards every method to that member.

Finally:

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


Building on guido's answer, here's one which disconnects all NHibernate dependencies from the repository interface. Quite a bit of boiler plate though and probably not a good technique if you want to make use of a lot of NHibernate specific functionality; then referencing the NHibernate.dll might be more appropriate.

First the interfaces:

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

And then the implementation:

    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
}


What I've done to work around this is to create a public virtual function in my repository to EagerlyFetch my object. Then, in my unit tests I use that Stub instead that passes through everything except for my EagerlyFetch method, which just returns a list. Here is an example of what I've done:

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

And then in my tests, I simply provide a PersonRepositoryStub:

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

This would be an alternative to some of the above answers (which I have not tried), but this works for me.

Cheers,

Levi


Alternatively wrap your test data IEnumerable in a stub that implements IFutureValue (NB this uses 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; }
    }
}

(I didn't write this, credit to a colleague far more skilled than myself)

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号