Nhibernateと汎用リポジトリを使用して.NETWebAPIアプリケーションを開発しています。現在、Ninjectを使用して依存性注入を正しく設定しようとしています。ただし、現在の構成にいくつかの問題があります。DALにダウンしてデータベースからデータをフェッチしようとする要求を行うときに、NHibernate ISession(以下のUnitOfWork.cs内)オブジェクトがnullになるか、すでに閉じられている場合があります。
なぜこれが発生するのか、コードの何が問題なのかを正確に把握することはできませんでした。Ninjectのスコーピング/バインディングがどういうわけか間違っていると思いましたが、機能させることができません。
これは私の現在の実装です(表示されるコードの量を減らすために、無関係なコードを取り除きました):
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);
}
顧客サービス
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;
}
}
要約すると、簡単なGetCustomerリクエストを作成します。CustomerControllerには、CustomerServiceインスタンスが注入されます。次に、CustomerServiceにRepositoryインスタンスが注入され、リポジトリ自体にUnitOfWork実装(UnitOfWorkFactoryクラスのメソッドBeginUnitOfWork()によって作成される)が注入されます。また、要求は最初に認証委任ハンドラー(基本認証用)によってインターセプトされることにも言及する価値があります。このハンドラーもCustomerServiceを使用します。
APIに(RESTクライアントやcURLなどを介して)リクエストを行うと、これは最初は機能しますが、時々(またはその後のリクエストで)、ISessionにアクセスしようとするとデータレイヤーでエラーが発生しますオブジェクト(NULL)であり、サーバーを再起動して再び機能させる必要があります。
明らかな何かを見逃したことがありますか?誰かがこれを解決する方法を説明できますか?ありがとう!
アップデート
私はこれをさらにデバッグしていて、UnitOfWorkがすべてのリクエストで適切にインスタンス化され、新しいISessionを取得していることを発見しました。ただし、場合によっては、UoW Dispose()メソッドが2回トリガーされます(スタックトレースによるNHibernateのキャッシング/プルーニングが原因)。これが、内部セッションオブジェクトがnullである理由です。この例外がトリガーされると、後続のすべてのリクエストで、Ninjectは明らかにこのnullセッションでUnitOfWorkの既存のインスタンスを検出します。:/