1

I'm new to NHibernate and not very good at C#, but I'm learning. I have a DataProvider class which provides data for my application using NHibernate 3. It's structured pretty much identical to Steve Bohlen's Summer of NHibernate videos.

I've noticed that I'm about to repeat my code a lot and I want to simplify my DataProvider. For example I have two business classes called Instrument and Broker. The method to add an Instrument in my DataProvider is:

    public int AddInstrument(Instrument instrument)
    {
        using (ITransaction tx = _session.BeginTransaction())
        {
            try
            {
                int newId = (int)_session.Save(instrument);
                _session.Flush();
                tx.Commit();
                return newId;
            }
            catch (NHibernate.HibernateException)
            {
                tx.Rollback();
                throw;
            }
        }
    }

and the AddBroker class looks remarkably similar (just find and replace). So I thought maybe I could use Generics to solve the problem. Something like:

public class DataProvider <TEntity>
{
    public int AddEntity(TEntity entity)
    {
        using (ITransaction tx = _session.BeginTransaction())
        {
            try
            {
                int newId = (int)_session.Save(entity);
                _session.Flush();
                tx.Commit();
                return newId;
            }
            catch (NHibernate.HibernateException)
            {
                tx.Rollback();
                throw;
            }
        }
    }    
}

With this I can pass in a Broker, an Instrument or anything and I save myself a lot of repetitive code. The problem I'm having is that in my Test class I create a new DataProvider each time a test is run like this:

    #region Fields

    private DataProvider _provider;
    private SessionManager _sessionManager;
    private NHibernate.ISession _session;

    #endregion

    [SetUp]
    public void Setup()
    {
        DatabaseSetUp();

        _session = _sessionManager.GetSession();
        _provider = new DataProvider(_session);    // problem here
    }

Using generics I have to pass in the object type to my DataProvider. Can you think of a way to solve this? I am a novice programmer and I am wondering if I am going down the right path. Should I be doing something totally different?

UPDATE

I have tried to implement Groo's answer but am having some issues. Here's what I've done.

IRepo.cs

interface IRepo<T>
{
    int Add<Entity>(Entity entity);
    void Delete<Entity>(Entity entity);
    void GetById<Entity>(int Id);
}

BaseRepo.cs

public abstract class BaseRepo <T> : IRepo <T>
{
    private ISession _session;

    #region SessionManagement

    public BaseRepo(ISession session)
    {
        _session = session;
    }

    public ISession Session
    {
        set { _session = value; }
    }

    #endregion

    public int Add<Entity>(Entity entity)
    {
        using (ITransaction tx = _session.BeginTransaction())
        {
            try
            {
                int newId = (int)_session.Save(entity);
                _session.Flush();
                tx.Commit();
                return newId;
            }
            catch (NHibernate.HibernateException)
            {
                tx.Rollback();
                throw;
            }
        }
    }

    // other methods omitted for brevity
}

IRepoFactory.cs

interface IRepoFactory
{
    IInstrumentRepo CreateInstrumentRepo(ISession s);
}

RepoFactory.cs

public class RepoFactory : IRepoFactory
{
    public IInstrumentRepo CreateInstrumentRepo(ISession s) // problem here
    {
        return new InstrumentRepo(s);
    }

}

IInstrumentRepo.cs

interface IInstrumentRepo : IRepo<Instrument>
{

}

InstrumentRepo.cs

public class InstrumentRepo : BaseRepo<Instrument>, IInstrumentRepo
{
    public InstrumentRepo(ISession s) : base(s) { }
}

In RepoFactory.cs I get this error:

Inconsistent accessibility: return type 'MooDB.Data.IInstrumentRepo' is less accessible than method 'MooDB.Data.RepoFactory.CreateInstrumentRepo(NHibernate.ISession)'

Any ideas what I'm missing?

Mark Allison
  • 6,838
  • 33
  • 102
  • 151
  • Does a non-generic `DataProvider` class even exist? Or are you getting a compile error? – vgru Jan 11 '12 at 16:53
  • I understand you are learning this, but as an FYI. abstracting Nh behind a DAO/repository really limits the potential of NH. not to mention and NH is the data layer abstraction, so adding your abstraction on top of it, only creates noise. – Jason Meckley Jan 11 '12 at 16:58
  • @Groo I am considering converting from non-generic to generic. – Mark Allison Jan 11 '12 at 17:06
  • @JasonMeckley: with a generic base class like this, it's doesn't add much work nor overhead, but it does allow you the freedom to maneuver if needed. There are times when it's useful to have specific queries for a specific entity at one place, instead of repeating them all over your code. – vgru Jan 11 '12 at 17:06
  • @Jason I don't really follow you. I simply want to keep my code compact and avoid duplication, hence I want to explore options to solve this problem. – Mark Allison Jan 11 '12 at 17:07
  • @Groo this is solved via named queries and DetachedCritreia or creating context specific objects to encapsulate the query. what is described in the SoNh series and other NH books are generic repositories which don't add value to the application. – Jason Meckley Jan 11 '12 at 17:13
  • @MarkAllison repositories can DRY up code, but the material your reading doesn't really do that. it just abstracts `ISessionFactory` and `ISession` away from the rest of your code. these are the abstractions, no need to abstract further. and with so much abstraction it can become easy to introduce bugs and difficult to understand exactly what NH is doing. – Jason Meckley Jan 11 '12 at 17:19
  • You need to mark the `MooDB.Data.IInstrumentRepo` interface definition with the `public` modifier. Compiler is telling you that `CreateInstrumentRepo` method is public, but its return type isn't, so callers wouldn't be able to access its contents. – vgru Jan 12 '12 at 12:00

3 Answers3

2

First of all, to address your Test Setup question: the term Repository might suggest that it should be a long lived, persistent object, but Repositories used in DAL operations should actually be lightweight stateless objects with short lifetimes: you instantiate one when you need it, and throw it away as soon as you're done. When you think about this is terms of performance, you can easily instantiate millions of them per second.

Combined with NHibernate's short lived Session instances, this is how your code is meant to look like when everything is in place:

using (var session = SessionManager.OpenSession())
{
    // create an instrument repo
    IInstrumentRepo instruments = DAL.RepoFactory.CreateInstrumentRepo(session);
    var guitar = instruments.Find(i => i.Type == "Guitar");

    // create a customer repo
    ICustomerRepo customers = DAL.RepoFactory.CreateCustomerRepo(session);
    var cust = customers.Find(c => c.Name == "Mark")

    // do something -> changes will be persisted by NH when session is disposed
    cust.Instruments.Add(guitar);
}

That's the general idea. Now, let me explain it in more detail:

  1. You may have noticed that each repo has its own interface, and is created through a repo factory. Using a factory to create repositories means you can easily create mock repo factories, which will create any custom implementation of a repository for testing.

  2. Each repo interface inherits from the base interface generic interface, IRepo<T>. This allows you to use a generic repository in 99% of cases, but still leave room to implement a custom query method specific to, say, Customer entities only:

    public interface IInstrumentRepo : IRepo<Instrument>
    { 
        // nothing to do here
    }
    
    public interface ICustomerRepo : IRepo<Customer>
    {
        // but we'll need a custom method here
        void FindByAddress(string address);
    }
    
    public interface IRepo<T> 
    {
        T GetById(object id);
        T Save(T item);
    }
    
  3. This means that your repo implementations will, in most cases, simply inherit from the base abstract class (I named it BaseRepo, but it's essentially what your DataProvider class does right now):

    class InstrumentRepo : BaseRepo<Instrument>, IInstrumentRepo
    {
        // no need to implement anything here except pass the session downwards
        public InstrumentRepo(ISession s) : base(s) { }
    }
    
  4. Your factory will simply need to instantiate the proper repository when asked:

    public class RepoFactory : IRepoFactory
    {
         public IInstrumentRepo CreateInstrumentRepo(ISession s)
         {
             return new InstumentRepo(s);
         }
    }
    
  5. And you will need to use the Singleton pattern in a, say, DAL class to hold the factory (there are slightly better ways to do this, using DI, but for now this will do just fine):

    public static class DAL 
    {
        // repo factory is pretty lightweight, so no need for fancy
        // singleton patterns
    
        private static readonly IRepoFactory _repoFactory = new RepoFactory();
        public static IRepoFactory RepoFactory
        { 
            get { return _repoFactory; }
        }
    }
    
vgru
  • 49,838
  • 16
  • 120
  • 201
  • That's great - thanks very much. I'm struggling with some of it, but I'll persist and see if I can make it work. – Mark Allison Jan 11 '12 at 20:50
  • I have an issue - please could someone take a look at my update in the question? Scratching my head. – Mark Allison Jan 12 '12 at 11:46
  • @Mark: interfaces are missing the `public` keyword. I will update. Note, however, that concrete classes (like `InstrumentRepo`) shouldn't be public: they are instantiated explicitly through the factory. I've also added some methods to the `IRepo` interface. – vgru Jan 12 '12 at 12:08
  • Great stuff all working fine now. Thanks for an excellent answer, much appreciated. – Mark Allison Jan 12 '12 at 12:30
  • @MarkAllison: I was just writing the response. How did you solve the "non-static" problem? You should not mark the method with `static`, but rather create a singleton instance which would return the `RepoFactory` (in a different class, so that you can swap its implementation easily). No methods which are dealing with DAL access should be made static. **I have updated the answer yet again.** – vgru Jan 12 '12 at 12:42
  • I solved it like this: `IInstrumentRepo instruments = new RepoFactory().CreateInstrumentRepo(_session);` – Mark Allison Jan 12 '12 at 13:19
  • I have a further question. I am implementing a `Currency` class and the primary key is a string. So when I use the base `GetByID<>` it doesn't work because an `int` is expected. – Mark Allison Jan 12 '12 at 17:32
  • @Mark: check my updates: it would be better if `RepoFactory` would not be instantiated every time, but instead a Singleton like shown above. That will make it easier to swap its implementation if needed. Regarding the key, I usually make all keys have the same type, but if your `Get` method accepted an `object` parameter I believe it would work fine (check the `IRepo` interface example above, `GetById` method). Also, you should use `T` as the parameter in the generic interface, **not** the `Entity` class. There is no need for your entities to inherit from the same class with NHibernate. – vgru Jan 12 '12 at 18:31
  • @Mark: also, there is no need for your `Add` method to return the id. – vgru Jan 12 '12 at 18:34
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/6664/discussion-between-mark-allison-and-groo) – Mark Allison Jan 12 '12 at 18:50
1

The answer to your question is Absolutely Yes! This is what generics are meant for.

You are in the right way.

This argument is really too long to discuss here but you can find a lot of usefull info in this article:

http://www.codeproject.com/KB/architecture/NHibernateBestPractices.aspx

It helps me a lot to create my generic nhibernate Dao

giammin
  • 18,620
  • 8
  • 71
  • 89
0

Your data provider class doesn't necessarily need to be generic - you can just make the AddEntity method itself generic. Then you instantiate a DataProvider instance, and call (for example) its AddEntity<Instrument> method. Your class would look like this:

public class DataProvider
{
    public int AddEntity<TEntity>(TEntity entity)
    {
        using (ITransaction tx = _session.BeginTransaction())
        {
            try
            {
                int newId = (int)_session.Save(entity);
                _session.Flush();
                tx.Commit();
                return newId;
            }
            catch (NHibernate.HibernateException)
            {
                tx.Rollback();
                throw;
            }
        }
    }    
}
David M
  • 71,481
  • 13
  • 158
  • 186
  • That's correct, but potentially not the best approach. This generic repo should serve as a base class which other repositories can inherit from and **extend** if needed. If you make its methods generic, then you limit all your DAL calls to a single class. A unit-of-work approach where each entity has its own repository is the usual way to do this, IMHO. – vgru Jan 11 '12 at 17:00
  • @DavidM Interesting approach, I like that. – Mark Allison Jan 11 '12 at 17:08
  • @Groo You seem to be knowledgeable on this subject, if you have time, could you post up your solution? – Mark Allison Jan 11 '12 at 17:08
  • @MarkAllison: sure, I'm about to get out, but will do it later. – vgru Jan 11 '12 at 17:17