Problem
I’m trying to figure out how to create a NHibernate
UnitOfWork
using .NET Core Dependency Injection. I was just hard-coding the connection string before moving it to appsettings.json configuration file. Is this form of creating a UOW in the service.scope valid?
It works, but I’m not sure if this is the intended way to do it.
Startup.cs
services.AddScoped<IUnitOfWork, UnitOfWorkV>( x=> { return new UnitOfWorkV(Configuration); });
UnitOfWorkV.cs
public UnitOfWorkV(IConfiguration configuration)
{
_sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(configuration.GetSection("ConnectionStrings:ConexionV").Value))
.ExposeConfiguration(ConfigureNhibernateValidator)
.Mappings(m =>
{
m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly());
m.HbmMappings.AddFromAssemblyOf<JobQueue>();
})
.BuildSessionFactory();
Session = _sessionFactory.OpenSession();
Session.FlushMode = FlushMode.Auto;
}
appsettings.json
{
"ConnectionStrings": {
"ConexionV": "Some connection string"
}
}
Solution
In some camps it is not advised to tightly couple your code to framework specific dependencies like IConfiguration
.
Based on the shown constructor it looks like the UnitOfWorkV
really needs the connection string and not the configuration. That late bound constructor dependency will only be realized when the class is being resolved.
Refactor the class to depend on what is actually needed.
public UnitOfWorkV(string connectionString) {
_sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString)
.ExposeConfiguration(ConfigureNhibernateValidator)
.Mappings(m => {
m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly());
m.HbmMappings.AddFromAssemblyOf<JobQueue>();
})
.BuildSessionFactory();
Session = _sessionFactory.OpenSession();
Session.FlushMode = FlushMode.Auto;
}
And get the connection string at start up,
var connectionString = Configuration.GetConnectionString("ConexionV");
injecting it explicitly into the class.
if(connectionString == null) //Fail early
throw new Exception("Missing connection string");
services.AddScoped<IUnitOfWork, UnitOfWorkV>(_ => new UnitOfWorkV(connectionString));
That way, any validation can be done immediately at startup instead of deferring to when resolving the actually class
After encountering a huge memory leak (14GB of memory usage after 24 hrs of new ISession
, each one created once the WS was called) with this approach we came to an extended answer to this, the thing is this is due to our UnitOfWork
and Repository
implementation we used to open the session once the object was created, with this new approach we rely on a singleton IUnitOfWork
and open the session by request.
Startup.cs
services.AddSingleton<IUnitOfWork, UnitOfWorkV>(x => { return new UnitOfWorkV(Configuration.GetSection("ConnectionStrings:ConexionV").Value); });
We changed .AddScoped()
to .AddSingleton()
UnitOfWorkV.cs
public UnitOfWorkVentas(string conexion)
{
// SE CREA EL SESSION FACTORY DE LA UNIDAD DE TRABAJO...
_sessionFactory = Fluently.Configure()
//Obtenemos la cadena de conexion del IConfiguration
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(conexion))
.ExposeConfiguration(ConfigureNhibernateValidator)
.Mappings(m =>
{
m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly());
m.HbmMappings.AddFromAssemblyOf<JobQueue>();
})
.BuildSessionFactory();
//Creamos la Session
}
Create a session from a connection string received in the constructor (Thanks for the recommendation of @Nkosi ).
public void OpenSession()
{
Session = _sessionFactory.OpenSession();
Session.FlushMode = FlushMode.Auto;
}
Create an OpenSession()
method in our UnitOfWork
class
RepositoryV.cs
public RepositoryVentas(IUnitOfWork unitOfWork)
{
_unitOfWork = (UnitOfWorkVentas)unitOfWork;
_unitOfWork.AbrirSesion();
}
Open the Session in the Repository constructor