Asp MVC problem configuring application with Windsor and NHibernate - c#

Im having issues configuring application using windsor, facilities and nhibernate.
Im getting this exception:
ObjectDisposedException: Session is closed
Shouldnt windsor take care of instantiating session per request and opening it when I have configuration like this? Could I miss some configuration?
Here is my confuguration:
public class PersistenceFacility : AbstractFacility
{
protected override void Init()
{
Configuration config = BuildDatabaseConfiguration();
Kernel.Register(
Component.For<ISessionFactory>()
.LifeStyle.Singleton
.UsingFactoryMethod(config.BuildSessionFactory),
Component.For<ISession>()
.LifeStyle.PerWebRequest
.UsingFactoryMethod(k => k.Resolve<ISessionFactory>().OpenSession()));
}
private Configuration BuildDatabaseConfiguration()
{
return Fluently.Configure()
.Database(SetupDatabase)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<RnUlice>())
.ExposeConfiguration(ConfigurePersistence)
.BuildConfiguration() ;
}
......
}

If your Repository<T> gets a ISession in its constructor and it's singleton (default lifestyle), then it will only work in the first request you call your repository. In subsequent requests the repository will still have the same ISession as in the first call (because repository is singleton), but that session is now closed and invalid to use, therefore the error you see.
This is why most of the time you don't want a singleton depending on other components with "shorter" lifestyles (like per-web-request or transient).
See this article for a more thorough analysis of common lifestyle issues.

I figured out what was wrong. I forgot to configure my repository lifestyle to Transient. I dont quite understand how this is a problem though.
container.Register(Component.For(typeof(IRepository<>))
.ImplementedBy(typeof(Repository<>)).LifeStyle.Transient);
I wonder what is the default lifestyle then? I was reading in docs that it is singleton?! How could that be a problem?

Related

NET 5 and EF: how to use AddPooledDbContextFactory in liu of DbContext in services

I recently came across AddPooledDbContextFactory concept as part of my NET 5 self-education piece and am keen to implement it properly. However, I am not sure how to use it with generics that I generally use.
Example of my current setup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TestDbContext>(
(s, o) => o.UseNpgsql(Configuration.GetConnectionString("DatabaseConnection"))
.UseLoggerFactory(s.GetRequiredService<ILoggerFactory>()));
// other code //
}
my repository generic:
public class Repository<T> : IRepository<T> where T
{
private readonly TestDbContext _dbContext;
public Repository(TestDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task Create(T entity)
{
await _dbContext.Set<T>().AddAsync(entity);
await _dbContext.SaveChangesAsync();
}
// other methods //
}
this is invoked in following manner as example:
public class WeatherForecastController : ControllerBase
{
private readonly IRepository<Test> testRepo;
public WeatherForecastController(IRepository<Test> testRepo)
{
this.testRepo= testRepo;
}
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> GetAsync()
{
await testRepo.Create(new Test { Name = "Superman" });
// other code
}
}
I would like to convert this to use the new AddPooledDbContextFactory concept but cannot find enough documentation to figure out how to do this.
Atm only thing that comes to mind is using statements at each method but that doesn't make sense.
Any advice on this?
Documentation is not yet complete and is in progress, you track this issue
https://github.com/dotnet/EntityFramework.Docs/issues/2523
You can also a look at the tests for AddPooledDbContextFactory to see how to register DbContext with
https://github.com/dotnet/efcore/search?q=AddPooledDbContextFactory
for example to register DbContext:
services.AddPooledDbContextFactory<TContext>(ob =>
ob.UseSqlServer("ConnectionString").EnableServiceProviderCaching(false), poolSize: 32)
Then in your class, inject an IDbContextFactory<TContext> and use it like this:
using(var context = _factory.CreateDbContext())
{
var orders = await context.Orders.Where(o => o.Id > 3).ToListAsync();
}
According to this post:
Note that the DbContext instances created in this way are not managed
by the application's service provider and therefore must be disposed
by the application
You can also check out this post to see how to use IDbContextFactory:
https://learn.microsoft.com/en-us/aspnet/core/blazor/blazor-server-ef-core?view=aspnetcore-5.0
#Aeseir your code looks good to me. You are following best practices and you don't need to change it.
You are using the Repository Pattern, so your Repository class has all of your query logic which helps create loosely coupled and maintainable code.
In your ConfigureServices, calling: services.AddDbContext<TestDbContext>() registers TestDbContext with Scoped service lifetime. This is the way that DbContext is designed to work, and it will also work well with ASP.NET controllers, since they have a Scoped lifetime as well.
You did not show your code for registering IRepository, but that service lifetime should be Scoped as well. Btw, you can tell BuildServiceProvider() to validate scope registrations:
builder.Services.BuildServiceProvider(validateScopes: true);
Since DbContext is designed to have a Scoped service lifetime, and since your IRepository and Controller services are Scoped as well, every request gets brand new:
Controller
IRepository
DbContext
Those services are used for the request and then Diposed. This is how ASP.NET is intended to work.
Apparently at some point, DbContext pooling has been introduced to improve performance. In this case, EF Core manages a pool of context instances for you and resets them after each request. This can improve performance, although in some situations, the benefit might be small. See MSDN documentation for more details.
I think for use with ASP.NET controllers (i.e. the code you posted above) all you need to do to take advantage of EF Core context pooling is call AddDbContextPool():
builder.Services.AddDbContextPool<ApplicationDbContext>(/* ... */);
However, if you needed to use DbContext in services registered with Singleton lifetime, then the pattern above would not work well. Because when a Scoped service gets used in a Singleton service, the Scoped service is essentially a Singleton. Each request would not get a new DbContext, nor a reset one from the pool. (See QUESTION below.)
In that case, you might want to use the DbContext factory pattern instead:
builder.Services.AddDbContextFactory<ApplicationDbContext>(/* ... */);
Or, if you want to use context pooling with a factory pattern:
builder.Services.AddPooledDbContextFactory<ApplicationDbContext>(/* ... */);
The DbContextFactory can then be used in other services through constructor injection. For example:
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
The injected factory can then be used to construct DbContext instances in the controller code. For example:
public void DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
Keep in mind that when you call CreateDbContext(), context instances are not managed by the service provider and therefore must be disposed by the application. Hence you need to Dispose of them yourself, such as in the example above which does so with the using statement.
QUESTION
I am doing my best to understand this stuff and explain it, but I might be wrong, so please call out an inaccuracies in my post.
When using AddDbContextPool(), does the DbContext get registered as a Singleton or Scoped?
I found in MSDN documentation that it's effectively registered as a Singleton:
Context pooling works by reusing the same context instance across requests; this means that it's effectively registered as a Singleton, and the same instance is reused across multiple requests (or DI scopes). This means that special care must be taken when the context involves any state that may change between requests.
However, I have found that if AddDbContextPool() is used along with true for validatedScopes:
builder.Services.BuildServiceProvider(validateScopes: true)
When DbContext is consumed from another service which is registered as a Singleton, the following exception is thrown:
System.InvalidOperationException: 'Cannot consume scoped service 'ApplicationDbContext' from singleton 'IRepository'.'
Hence why I stated above that DbContext still gets Scoped service lifetime.

Entity Framework Core - disposed when trying to run 2nd Query

I have an issue with a database context being disposed. I have set up the databases like the below in the Configure services method. The code has been simplified to hopefully make it easier to read.
public void ConfigureServices(IServicesCollection services)
{
Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database1")));
Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database2")));
Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database3")));
Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database4")));
Services.AddScoped<IQueryService1, ConcreteService1>();
Services.AddScoped<IQueryService1, ConcreteService2>();
Services.AddScoped<IQueryService1, ConcreteService3>();
Services.AddScoped<IQueryService1, ConcreteService4>();
}
Now in one of the controllers I inject the relevant services that are required.
[Produces("application/json")]
[Route("api/[controller]/[action]
public class DigitalFinesController : Controller
{
private readonly IQueryService1 _Service1;
public DigitalFinesController(IConfiguration configuration, IQueryServices1 QueryService1)
{
_Service1 = QueryService1;
}
[Authorize]
[HttpPost]
[ActionName("SubmitFine")]
[ProducesResponseType(200)]
[ProducesResponseType(401)]
public async Task<IActionResult> SubmitFine([FromBody] Models.DigitalFine fine)
{
//This is a simple version of my issue
var vehicles = _Service1.Vehicles.FirstOrDefault(p=> p.vrm == "OX7 DFG");
if(vehicle == null)
{
return BadRequest("Vehicle is missing");
}
var fleet = _Service1.Fleets.FirstOrDefault(p=> p.Code = "MyCode");
}
}
And once I get to the second query I get the following exception
System.ObjectDisposedException: 'Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
And I am stumped to understand why this is happening. Can anyone please give me a pointer to fix this?
Many thanks
Simon
I think it may have to do with how you're registering it. Try registering it with AddSingleton instead of AddScoped
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes-and-registration-options
Scoped
Scoped lifetime services are created once per request.
Warning
When using a scoped service in a middleware, inject the service into the Invoke or InvokeAsync method. Don't inject via constructor injection because it forces the service to behave like a singleton. For more information, see ASP.NET Core Middleware.
Singleton
Singleton lifetime services are created the first time they're requested (or when ConfigureServices is run and an instance is specified with the service registration). Every subsequent request uses the same instance. If the app requires singleton behaviour, allowing the service container to manage the service's lifetime is recommended. Don't implement the singleton design pattern and provide user code to manage the object's lifetime in the class.
Warning
It's dangerous to resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
.AddScoped will dispose after lifetime of the request, try changing to singleton or transient:
Please see documentation:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2
At this section:
In the sample app, the IMyDependency service is registered with the
concrete type MyDependency. The registration scopes the service
lifetime to the lifetime of a single request. Service lifetimes are
described later in this topic.
Warning
When using a scoped service in a middleware, inject the service into
the Invoke or InvokeAsync method. Don't inject via constructor
injection because it forces the service to behave like a singleton.
For more information, see ASP.NET Core Middleware.

Is changing contextLifetime to Singleton a correct solution to fix the following error?

I am using Asp.net Core 2. Consider the following classes:
public class BlogDbContext: DbContext
{
.....
}
public interface IBlogData { ... }
public class BlogData : IBlogData
{
private BlogDbContext _context;
public BlogData(BlogDbContext context) { ... }
.......
}
When I used the default value contextLifetime: ServiceLifetime.Scoped as follows,
public void ConfigureServices(IServiceCollection services)
{
.....
services.AddDbContext<BlogDbContext>(...);
.....
services.AddSingleton<IBlogData, BlogData>();
}
Compilation, first migration and first database update were performed without any error. But I got the following error when visiting the page for the first time.
InvalidOperationException: Cannot consume scoped service 'MyProject.Data.BlogDbContext' from singleton 'MyProject.Services.IBlogData'.
Question
Is it correct if I fix the error by changing contextLifetime as follows ?
public void ConfigureServices(IServiceCollection services)
{
.....
services.AddDbContext<BlogDbContext>(...,contextLifetime: ServiceLifetime.Singleton);
.....
services.AddSingleton<IBlogData, BlogData>();
}
Note: this problem is specific to Asp.Net Core 2.0.
It's because you are trying to use a scoped service from a singleton service.
This is new to asp.net core 2.0. Only singleton services can be consumed by a
signleton service.
You need to add BlogData as Scoped.
No, you should generally always used scoped for DbContext in asp.net core that way it gets created once per request and is automatically disposed for you at the end of the request.
You are not really showing the code where the error is happening, but my guess is it is happening because you are running some code in startup to run the migrations. If you confirm that or show the code where the error is actually happening I could offer more help
Old question without a great answer.
The reason you get this error is because a scoped service has to be recreated every time a new page request is made (Atleast within ASP.net). How it does that is everytime a service is "requested" by way of injection (For example within a constructor), it caches the first time it's requested, then for subsequent requests, it simply returns that same instance. At the end of the page load, it trashes this instance.
Now a singleton is instead cached that first time it's requested, but it's never disposed. Every request ever made for the service will return the exact same instance.
The problem is if you have a parent service that is singleton that then references a child service that is scoped. That first request, the parent and child are created. The second request, well the parent is a singleton so it isn't recreated. Now we have a problem, because the child service is scoped, how can it be created for each request if the thing that is requesting it (And thus kicking off the DI), is a singleton? It can't. So an exception is thrown.
Interestingly, it is more about saving yourself from hanging yourself more than anything. For example, if you replace the scoped instance with a transient one, you will still have the same "problem", but it won't throw an exception.
More info here if you need further examples : https://dotnetcoretutorials.com/2018/03/20/cannot-consume-scoped-service-from-singleton-a-lesson-in-asp-net-core-di-scopes/

How to implement nhibernate session per request pattern, using Service stack's funq container as a dependency injector

I am getting the session by injecting session to service method in global.asax as
1
protected void Application_Start()
{
this.RegisterContainer();
}
2
private void RegisterContainer()
{
container.Register<IActivityService>(c => new ActivityService(SessionFactory.GetCurrentSession()));
}
3 In service method i am getting the session as
using (var transaction = _session.BeginTransaction())
{
........................
}
the problem is when concurrent requests came to this service method, it is throwing exceptions.I came to know that Nhibernate is not supporting concurency.Ho to achieve it using Funq Container?
By default ServiceStack's IOC registers dependencies as a singleton by default whereas you should register a transient dependency for this instead with:
container.Register<IActivityService>(c =>
new ActivityService(SessionFactory.GetCurrentSession()))
.ReusedWithin(ReuseScope.None);
Also this previous question shows other registration examples using NHibernate with ServiceStack.

NServiceBus: NhibernateMessageModule Implementation

I am using NserviceBus 2.5 and was facing the problem NSB caching the Nhibernate Sessions.
I spent sometime on internet and found that class implementing IMessageModule interface is the way to solve this. I also saw the implementation of such at https://github.com/NServiceBus/NServiceBus/blob/v2.5/src/impl/SagaPersisters/NHibernateSagaPersister/NServiceBus.SagaPersisters.NHibernate/NHibernateMessageModule.cs
MyEndPoint is defined like
public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, IWantCustomLogging, IWantCustomInitialization
{
public void Init()
{
var location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
var windsorContainer = IoCBootstrapper.InitializeForSession(Path.Combine(location, "MyDll.config")); //This line creates Windsor container without Nhibernate Session I have not written real dll.config name but my code contains
ISessionFactory sessionFactory = MessageSessionFactory.ConfigureSessionFactory();
windsorContainer.Kernel.AddComponentInstance<ISessionFactory>(sessionFactory);
windsorContainer.Register(Component.For(typeof(NHibernateMessageModule)).LifeStyle.Singleton);
windsorContainer.Register(Component.For(typeof(MessageHandler)).LifeStyle.Transient);
NServiceBus.Configure.With(AllAssemblies.Except("XYZ.dll"))
.CastleWindsorBuilder(windsorContainer)
.XmlSerializer()
.MsmqTransport()
.UnicastBus()
.LoadMessageHandlers();
SetLoggingLibrary.Log4Net(log4net.Config.XmlConfigurator.Configure);
}
}
I have defined following class to create SessionFactory
public class MessageSessionFactory
{
protected static ISessionFactory sessionFactory;
private static ILog log = LogManager.GetLogger(typeof(MessageSessionFactory));
public static ISessionFactory ConfigureSessionFactory()
{
try
{
if (sessionFactory != null) return sessionFactory;
string connectionString = System.Configuration.ConfigurationManager
.ConnectionStrings["SessionFactoryCS"].ToString();
NHibernate.Cfg.Configuration nHibernateConfiguration =
new NHibernate.Cfg.Configuration();
nHibernateConfiguration.SetProperty(
NHibernate.Cfg.Environment.ProxyFactoryFactoryClass,
typeof(NHibernate.ByteCode.Castle.ProxyFactoryFactory).AssemblyQualifiedName);
nHibernateConfiguration.SetProperty(
NHibernate.Cfg.Environment.Dialect,
typeof(NHibernate.Dialect.MsSql2005Dialect).AssemblyQualifiedName);
nHibernateConfiguration.SetProperty(
NHibernate.Cfg.Environment.ConnectionString, connectionString);
nHibernateConfiguration.SetProperty(
NHibernate.Cfg.Environment.FormatSql, "true");
nHibernateConfiguration.SetProperty(NHibernate.Cfg.Environment.CurrentSessionContextClass,
typeof(NHibernate.Context.ThreadStaticSessionContext).AssemblyQualifiedName);
nHibernateConfiguration.AddAssembly(Assembly.GetCallingAssembly());
sessionFactory = nHibernateConfiguration
.BuildSessionFactory();
return sessionFactory;
}
catch (TypeInitializationException ex)
{
throw new Exception("TO DO :Enter message");
}
}
}
Whenever I try to start the service I see messages like at HandleEndMessage
NHibernate.HibernateException: No current session context configured.
at NHibernate.Context.CurrentSessionContext.GetCurrentSessionContext(ISessionFactory factory)
at NHibernate.Context.CurrentSessionContext.HasBind(ISessionFactory factory)
If I catch the exception here then this error shifts to HandleError
Could you anybody tell me where I could be wrong?
that message means that you haven't configured nhibernate to tell it how to use contextual sessions. the nhibernate contextual sessions feature means that nhibernate will manage keeping track of the current session for you and you only need to worry about binding and unbinding the current session to/from the context and any time you ask the session factory for the current session within that context, you will get the same one. the message module andreas wrote makes use of this feature (and you should too in your handlers if that is how you are managing your sessions - meaning that if you have a dependency in your handler classes on ISessionFactory and get sessions from there, you should use ISessionFactory.GetCurrentSession() instead of ISessionFactory.OpenSesion()).
to fix the problem you are seeing, you need to tell NHibernate how to manage the session context. there are several built in options. the one andreas recommends in his blog post is ThreadStatic. this is fine in your case, as it seems you are only connecting to one database and using one session factory. note that this context class only supports one session factory, so it wouldn't work if you are dealing with more than one. thread static means each thread will have its own session context - you will get the same session as long as you are on the same thread. this works nicely with nservicebus as the handler will execute entirely on a thread and the message module will make sure you are getting a new session with each message and not using the same one from the previous message handled by that thread.
to configure nhibernate for this, you need to set the current_session_context_class property to thread_static. if you are configuring nhibernate directly, you know how you are doing it. if you are using fluent nhibernate, you will need to use the FluentConfiguration.ExposeConfiguration method to do this:
Fluently.Configure()
// whatever else you are doing
.ExposeConfiguration(
c => c.SetProperty("current_session_context_class", "thread_static")
);
here is andreas's post about it:
http://andreasohlund.net/2010/02/03/nhibernate-session-management-in-nservicebus/

Categories