Je développe une application Web API .NET en utilisant Nhibernate et un référentiel générique. J'essaie maintenant de configurer correctement l'injection de dépendances à l'aide de Ninject. Cependant, j'ai quelques problèmes avec ma configuration actuelle : occasionnellement, mon objet NHibernate ISession (dans UnitOfWork.cs ci-dessous) est soit nul, soit déjà fermé lors d'une requête qui va vers le DAL et tente de récupérer des données de la base de données.
Je n'ai pas réussi à comprendre exactement pourquoi cela se produit ou ce qui ne va pas dans mon code. J'ai pensé que mon scoping/binding Ninject était en quelque sorte incorrect, mais je n'arrive pas à le faire fonctionner.
Voici ma mise en œuvre actuelle (j'ai supprimé le code non pertinent pour réduire la quantité de code affichée) :
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);
}
}
ICerService.cs
public interface ICustomerService
{
Customer GetCustomer(string id);
}
Service clientèle
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;
}
}
Pour résumer en quelques mots : Je fais une simple requête GetCustomer. Le CustomerController est injecté avec une instance de CustomerService. Le CustomerService est ensuite injecté avec une instance de Repository, et le repository lui-même est injecté avec une implémentation de UnitOfWork (qui est créée par la méthode BeginUnitOfWork() dans la classe UnitOfWorkFactory). Il convient également de mentionner que la demande est d'abord interceptée par un gestionnaire de délégation d'authentification (pour l'authentification de base). Ce gestionnaire utilise également la méthode CustomerService.
Lorsque je fais des requêtes à l'API (via un client REST ou cURL ou autre), cela fonctionne initialement, mais de temps en temps (ou lors d'une requête ultérieure), j'obtiens une erreur dans la couche de données lorsque j'essaie d'accéder à l'objet ISession (NULL), et je dois redémarrer le serveur pour que cela fonctionne à nouveau.
Ai-je manqué quelque chose d'évident ? Quelqu'un peut-il m'expliquer comment résoudre ce problème ? Merci de votre compréhension.
Mise à jour
J'ai poursuivi le débogage et j'ai découvert que mon UnitOfWork est correctement instancié à chaque demande et qu'il reçoit donc un nouvel ISession. Mais dans certains cas, la méthode Dispose()de l'UoW est déclenchée deux fois (en raison d'une certaine mise en cache/élagage de NHibernate, d'après la trace de pile). C'est pourquoi l'objet de session interne est nul. Une fois que cette exception est déclenchée, dans toutes les requêtes suivantes, Ninject trouve évidemment une instance déjà existante de UnitOfWork, avec cette session nulle :/