2
votes

I'm developing a .NET Web API application using Nhibernate and a generic repository. Now I'm trying to correctly setup dependency injection using Ninject. However, I have some problems with my current configuration: occasionally my NHibernate ISession (in UnitOfWork.cs below) object is either null or already closed when making a request that goes down to the DAL and tries to fetch data from the database.

I have not been able to figure out exactly why this happens or what is wrong in my code. I thought my Ninject scoping/binding was somehow incorrect, but cannot get it to work.

This is my current implementation (I have stripped away irrelevant code to reduce the amount of code displayed):

NinjectWebCommon.cs

private static void RegisterServices(IKernel kernel)
{
   UnitOfWorkFactory uow = new UnitOfWorkFactory(
      ConfigurationManager.ConnectionStrings["foo"].ConnectionString,
      Assembly.GetExecutingAssembly());

   kernel.Bind<IUnitOfWorkFactory>().ToConstant(uow).InSingletonScope();
   kernel.Bind<IUnitOfWork>().ToMethod(f => f.Kernel.Get<IUnitOfWorkFactory().BeginUnitOfWork()).InRequestScope();

   // Services
   kernel.Bind<ICustomerService>().To<CustomerService>().InRequestScope();

   // Repositories
   kernel.Bind(typeof(IRepository<,>)).To(typeof(Repository<,>)).InRequestScope();

   // Used for Basic Auth (uses customer Service)
   kernel.Bind<IPrincipalProvider>().To<MyPrincipalProvider>().InRequestScope();
}

IUnitOfWorkFactory.cs

public interface IUnitOfWorkFactory : IDisposable
{
    IUnitOfWork BeginUnitOfWork();
    void EndUnitOfWork(IUnitOfWork unitOfWork);
}

UnitOfWorkFactory.cs

public class UnitOfWorkFactory : IUnitOfWorkFactory
{
    public UnitOfWorkFactory(string connectionString, Assembly assembly)
    {
        var rawCfg = new Configuration();
        rawCfg.SetNamingStrategy(new MsSql2005NamingStrategy());
        var cfg = Fluently
            .Configure(rawCfg)                .Database(FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2005.ConnectionString(connectionString))
            .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()));

        Configuration = cfg.BuildConfiguration();
        SessionFactory = Configuration.BuildSessionFactory();
    }

    protected ISessionFactory SessionFactory { get; private set; }

    protected Configuration Configuration { get; private set; }

    public IUnitOfWork BeginUnitOfWork()
    {
        return new UnitOfWork(this.SessionFactory.OpenSession());
    }

    public void EndUnitOfWork(IUnitOfWork unitOfWork)
    {
        var nhUnitOfWork = unitOfWork as UnitOfWork;
        if (unitOfWork != null)
        {
            unitOfWork.Dispose();
            unitOfWork = null;
        }
    }

    public void Dispose()
    {
        if (this.SessionFactory != null)
        {
            (this.SessionFactory as IDisposable).Dispose();
            this.SessionFactory = null;
            this.Configuration = null;
        }
    }
}

IUnitOfWork.cs

public interface IUnitOfWork : IDisposable
{
    TEntity GetSingle<TEntity>(Expression<Func<TEntity, bool>> expression) where   TEntity : class;
}

UnitOfWork.cs

public class UnitOfWork : IUnitOfWork
{
   public UnitOfWork(NHiberante.ISession session)
   {
      if (session == null)
      {
         throw new ArgumentNullException("session");
      }
      this.Session = session;
   }

   public NHiberante.ISession Session { get; private set; }

   private IQueryable<TEntity> Set<TEntity>() where  TEntity : class
   {
      return Session.Query<TEntity>();
   }

   public TEntity GetSingle<TEntity>(Expression<Func<TEntity, bool>> expression)  where TEntity : class
   {
      return Set<TEntity>().SingleOrDefault(expression);
   }

   public void Dispose()
   {
      if ( this.Session != null )
      {
         (this.Session as IDisposable).Dispose();
         this.Session = null;
      }
   }
}

IRepository.cs

public interface IRepository<TEntity, TPrimaryKey> where TEntity : class
{
    TEntity GetSingle(Expression<Func<TEntity, bool>> expression);
}

Repository.cs

public class Repository<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey> where TEntity : class
{
   public Repository(IUnitOfWork unitOfWork)
   {
      if (unitOfWork == null)
      {
         throw new ArgumentNullException("unitOfWork");
      }
      this.UnitOfWork = unitOfWork;
   }

   protected IUnitOfWork UnitOfWork { get; private set; }

   public virtual TEntity GetSingle(Expression<Func<TEntity, bool>> expression)
   {
      return UnitOfWork.GetSingle(expression);
   }
}

ICustomerService.cs

public interface ICustomerService
{
    Customer GetCustomer(string id);
}

CustomerService

public class CustomerService : ICustomerService
{
    private readonly IRepository<Customer, string> _customerRepo;

    public CustomerService(IRepository<Customer, string> customerRepo)
    {
        _customerRepo = customerRepo;
    }

    public Customer GetCustomer(string id)
    {
        return _customerRepo.GetSingle(l => l.ID == id);
    }
}

CustomerController.cs

public class CustomerController : ApiController
{
    private ICustomerService _customerService;

    public CustomerController(ICustomerService customerService)
    {
        _customerService = customerService;
    }

    public string Get(string id)
    {
        var customer = _customerService.GetCustomer(id);
        return customer.Name;
    }
}

To summarize in words: I make a simple GetCustomer request. The CustomerController is injected with a CustomerService instance. The CustomerService is then injected with a Repository instance, and the repository itself in injected with a UnitOfWork implementation (which is created by the method BeginUnitOfWork() in UnitOfWorkFactory class). Also worth mentioning is that the request is first intercepted by an authentication delegating handler (for basic authentication). This handler also uses the CustomerService.

When making requests to the API, (through a REST client or cURL or whatever) this initially works, but every now and then (or at some later subsequent request), I get an error down in the data layer when trying to access the ISession object (being NULL), and I must restart the server to get it working again.

Have I missed something obvious? Can anyone explain how to solve this? Thanks!

Update

I have been debugging this further and found out that my UnitOfWork is properly instantiated at every request and thus getting a new ISession. But in some cases the UoW Dispose()-method is triggered twice (due to some NHibernate caching/pruning according to the stack trace). That's why the internal session object is null. Once this exception is triggered, in all subsequent requests, Ninject obviously finds an already existing instance of UnitOfWork, with this null-session. :/

1
How is this problem ninject specific? You are not using ninject to instantiate the session. For WCF, REST etc. you need to use the specific session context: see nhforge.org/wikis/reference2-0en/context-sessions.aspx or build your own. Also see my post about generic repos: planetgeek.ch/2012/05/05/… the implementation could even be simplified with TransactionScope.Daniel Marbach
I thought this was Ninject-specific in the sense that Ninject will inject the IUnitOfWork interface with the UnitOfWork implementation, which takes an ISession instance in the ctor. This is managed by the following: kernel.Bind<IUnitOfWork>().ToMethod(f => f.Kernel.Get<IUnitOfWorkFactory().BeginUnitOfWork()).InRequestScope(); I have not seen any tutorials where I need to use context sessions. Do I really?anve

1 Answers

1
votes

The ISession object is not managed by ninject but by NH in your sample. Therefore your IUnitOfWork can live longer than the session managed by NH. Either manage everything with Ninject or use the appropriate session context. The problem root cause is in BeginUnitOfWork where you newing up your unit of work and use SessionFactory.OpenSession().