Nick
Nick

Reputation: 1891

Multiple databases using repo pattern with ninject and nhibernate

I am using the repository/unit of work pattern with nhibernate and ninject. I have a generic repository and the unit of work provides the session factory. It has been working great so far but now I have hit a wall. My project now requires that I have a second database. I cannot wrap my head around how to make my repo/unit of work generic for the databases as well. Here is my code, obtained from here:

Repository:

public interface IRepository<T> where T : class
{
    IQueryable<T> GetAll();
    T GetById(Guid id);
    void Create(T entity);
    void Update(T entity);
    void Delete(Guid id);
}

public class Repository<T> : IRepository<T> where T : class
{
    private UnitOfWork _unitOfWork;
    public Repository(IUnitOfWork unitOfWork)
    {
        _unitOfWork = (UnitOfWork)unitOfWork;
    }

    protected ISession Session { get { return _unitOfWork.Session; } }

    // CRUD operations...
}

Unit of Work:

public interface IUnitOfWork
{
    void BeginTransaction();
    void Commit();
}

public class UnitOfWork : IUnitOfWork
{
    private static readonly ISessionFactory _sessionFactory;
    private ITransaction _transaction;

    public ISession Session { get; private set; }

    static UnitOfWork()
    {
        // Initialise singleton instance of ISessionFactory, static constructors are only executed once during the
        // application lifetime - the first time the UnitOfWork class is used

        _sessionFactory = Fluently.Configure()
                       .Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("CONN")))
                       .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.Load("MyAssembly")))
                       .CurrentSessionContext<WebSessionContext>()
                       .BuildSessionFactory();
    }

    public UnitOfWork()
    {
        Session = _sessionFactory.OpenSession();
    }

    public void BeginTransaction()
    {
        _transaction = Session.BeginTransaction();
    }

    public void Commit()
    {
        try
        {
            _transaction.Commit();
        }
        catch
        {
            _transaction.Rollback();
            throw;
        }
        finally
        {
            Session.Close();
        }
    }
}

My only idea is to have separate repository and unit of work classes for each of the databases. This seems really ugly to me as it will be a lot of duplication of code. Is there a way to make the repo/uow generic for the database level as well as the entity type?

My other option that I have seen is that NCommon may be able to handle this for me. I am willing to take that route if that is recommended, but I wasn't ready to jump into bed with it right away as the project has not been updated in over 2 years.

Upvotes: 1

Views: 471

Answers (2)

Ram Y
Ram Y

Reputation: 2044

My solution is the same as Nick but I'm remove from the repository to know which unit of work but instead i use attribute on the Entity to know which unit of work it belong to (i.e. which database). A better solution will be check the Entity mapping but I didn't try it so I don't know if its possible.

the repository interface

public interface IRepository<T> where T : class
{
    Task<object> SaveAsync(T entity, CancellationToken token);

    Task SaveOrUpdateAsync(T entity, CancellationToken token);
    Task<T> LoadAsync(object id, CancellationToken token);
    Task<T> GetAsync(object id, CancellationToken token);
}

the repository implementation - i use Autofac Keyed service. and on the ctor i inject my available unit of work. if no attribute is on the class i assume it's the primary db i need.

public class NHibernateRepository<T> : IRepository<T> where T : class
{
    protected readonly ISession Session;
    private readonly IUnitOfWork _unitOfWork;

    public NHibernateRepository(IIndex<Core.Enum.Database, IUnitOfWork> unitOfWorks)
    {
        var att = typeof(T).GetCustomAttribute<DbAttribute>();
        _unitOfWork = unitOfWorks[att?.Database ?? Core.Enum.Database.System];
        Session = _unitOfWork.Session;
    }

    public Task<T> LoadAsync(object id, CancellationToken token)
    {
        return Session.LoadAsync<T>(id, token);
    }

    public Task<T> GetAsync(object id, CancellationToken token)
    {
        return Session.GetAsync<T>(id, token);
    }

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

    public Task<object> SaveAsync(T entity, CancellationToken token)
    {
        _unitOfWork.FlagCommit();
        return Session.SaveAsync(entity, token);
    }

    public Task SaveOrUpdateAsync(T entity, CancellationToken token)
    {
        _unitOfWork.FlagCommit();
        return Session.SaveOrUpdateAsync(entity, token);
    }
}

the configuration of the autofac is :

builder.RegisterType<UnitOfWorkFactoryMain>().SingleInstance();
        builder.RegisterType<UnitOfWorkFactorySecondary>().SingleInstance();

        builder.Register(c => new UnitOfWork(c.Resolve<UnitOfWorkFactoryMain>()))
            .Keyed<IUnitOfWork>(Database.System).InstancePerLifetimeScope();
        builder.Register(c => new UnitOfWork(c.Resolve<UnitOfWorkFactorySecondary>()))
            .Keyed<IUnitOfWork>(Database.Secondary).InstancePerLifetimeScope();

the rest is like Nick answer

Upvotes: 1

Nick
Nick

Reputation: 1891

Took me a while but here's what I came up with in case anyone else needs this:

Modified Repo:

public interface IRepository<TEntity, TContext> where TEntity : class where TContext : DatabaseContext
{
    IQueryable<TEntity> GetAll();
    TEntity GetById(Guid id);
    void Create(TEntity entity);
    void Update(TEntity entity);
    void Delete(Guid id);
}

public class Repository<TEntity, TContext> : IRepository<TEntity, TContext> where TEntity : class where TContext : DatabaseContext
{
    private UnitOfWork<TContext> _unitOfWork;
    public Repository(IUnitOfWork<TContext> unitOfWork)
    {
        _unitOfWork = (UnitOfWork<TContext>)unitOfWork;
    }

    protected ISession Session { get { return _unitOfWork.Session; } }

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

    public TEntity GetById(Guid id)
    {
        return Session.Get<TEntity>(id);
    }

    public void Create(TEntity entity)
    {
        Session.Save(entity);
    }

    public void Update(TEntity entity)
    {
        Session.Update(entity);
    }

    public void Delete(Guid id)
    {
        Session.Delete(Session.Load<TEntity>(id));
    }
}

Modified Unit of Work:

public class UnitOfWork<TContext> : IUnitOfWork<TContext> where TContext : DatabaseContext
{
    private ISessionFactory _sessionFactory;
    private ITransaction _transaction;

    public ISession Session { get; private set; }

    public UnitOfWork(TContext context)
    {
        if (_sessionFactory == null)
        {
            _sessionFactory = context.GetSessionFactory();
        }

        Session = _sessionFactory.OpenSession();

    }

    public void BeginTransaction()
    {
        _transaction = Session.BeginTransaction();
    }

    public void Commit()
    {
        try
        {
            _transaction.Commit();
        }
        catch
        {
            _transaction.Rollback();
            throw;
        }
        finally
        {
            Session.Close();
        }
    }
}

DatabaseContext:

public interface DatabaseContext
{
    ISessionFactory GetSessionFactory();
}

public class QualityControlDatabaseContext : DatabaseContext
{
    public ISessionFactory GetSessionFactory()
    {

        return Fluently.Configure()
                      .Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("QCConnection")))
                      .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.Load("QCEntities")))
                      .CurrentSessionContext<WebSessionContext>()
                      .BuildSessionFactory();
    }
}
public class SAPDatabaseContext : DatabaseContext
{
    public ISessionFactory GetSessionFactory()
    {

        return Fluently.Configure()
                      .Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("SAPConnection")))
                      .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.Load("SAPEntities")))
                      .CurrentSessionContext<WebSessionContext>()
                      .BuildSessionFactory();
    }
}

Upvotes: 2

Related Questions