There’s something which I am doing that is working, but I think it can probably be done a lot better (and therefore, with more maintainability).
I am using Ninject to inject various things into a controller. The problem which I needed to solve is that the DbContext for each repository needed to be the same. That is, the same object in memory.
Whilst, the following code does achieve that, my Ninject common config file has started to get quite messy as I have to write similar code for each controller:
kernel.Bind<OrderController>().ToMethod(ctx =>
{
var sharedContext = ctx.Kernel.Get<TTSWebinarsContext>();
var userAccountService = kernel.Get<UserAccountService>();
ILogger logger = new Log4NetLogger(typeof(Nml.OrderController));
ILogger loggerForOrderManagementService = new Log4NetLogger(typeof(OrderManagementService));
var orderManagementService = new OrderManagementService(
new AffiliateRepository(sharedContext),
new RegTypeRepository(sharedContext),
new OrderRepository(sharedContext),
new RefDataRepository(),
new WebUserRepository(sharedContext),
new WebinarRepository(sharedContext),
loggerForOrderManagementService,
ttsConfig
);
var membershipService = new MembershipService(
new InstitutionRepository(sharedContext),
new RefDataRepository(),
new SamAuthenticationService(userAccountService),
userAccountService,
new WebUserRepository(sharedContext)
);
return new OrderController(membershipService, orderManagementService, kernel.Get<IStateService>(), logger);
}).InRequestScope();
Is there a neater way of doing this?
Edit
Tried the following code. As soon as I make a second request, an exception is chucked that the DbContext has already been disposed.
kernel.Bind<TTSWebinarsContext>().ToSelf().InRequestScope();
string baseUrl = HttpRuntime.AppDomainAppPath;
kernel.Bind<IStateService>().To<StateService>().InRequestScope();
kernel.Bind<IRefDataRepository>().To<RefDataRepository>().InRequestScope().WithConstructorArgument("context", kernel.Get<TTSWebinarsContext>());
var config = MembershipRebootConfig.Create(baseUrl, kernel.Get<IStateService>(), kernel.Get<IRefDataRepository>());
var ttsConfig = TtsConfig.Create(baseUrl);
kernel.Bind<MembershipRebootConfiguration>().ToConstant(config);
kernel.Bind<TtsConfiguration>().ToConstant(ttsConfig);
kernel.Bind<IAffiliateRepository>().To<AffiliateRepository>().InRequestScope().WithConstructorArgument("context", kernel.Get<TTSWebinarsContext>());
kernel.Bind<IWebinarRepository>().To<WebinarRepository>().InRequestScope().WithConstructorArgument("context", kernel.Get<TTSWebinarsContext>());
kernel.Bind<IWebUserRepository>().To<WebUserRepository>().InRequestScope().WithConstructorArgument("context", kernel.Get<TTSWebinarsContext>());
kernel.Bind<IOrderRepository>().To<OrderRepository>().InRequestScope().WithConstructorArgument("context", kernel.Get<TTSWebinarsContext>());
kernel.Bind<IInstitutionRepository>().To<InstitutionRepository>().WithConstructorArgument("context", kernel.Get<TTSWebinarsContext>());
kernel.Bind<IUserAccountRepository>().To<DefaultUserAccountRepository>().InRequestScope();
kernel.Bind<IRegTypeRepository>().To<RegTypeRepository>().InRequestScope().WithConstructorArgument("context", kernel.Get<TTSWebinarsContext>());
kernel.Bind<UserAccountService>().ToMethod(ctx =>
{
var userAccountService = new UserAccountService(config, ctx.Kernel.Get<IUserAccountRepository>());
return userAccountService;
});
kernel.Bind<IOrderManagementService>().To<OrderManagementService>().InRequestScope();
//RegisterControllers(kernel, ttsConfig);
kernel.Bind<AuthenticationService>().To<SamAuthenticationService>().InRequestScope();
kernel.Bind<IMembershipService>().To<MembershipService>().InRequestScope();
There's something about InRequestScope I'm misunderstanding.
Edit:
.InRequestScope() will ensure everything which gets injected that binding will receive exactly the same instance when during injection (creation) the HttpContext.Current is the same. That means when a client makes a request and the kernel is asked to provide instances with .InRequestScope(), it will return the same instance for the exact same request. Now when a client makes another request, another unique instance will be created.
When the request ends, ninject will dispose the instance in case it implements IDisposable.
However consider the following scenario:
public class A
{
private readonly DbContext dbContext;
public A(DbContext dbContext)
{
this.dbContext = dbContext;
}
}
and binding:
IBindingRoot.Bind<DbContext>().ToSelf().InRequestScope();
IBindingRoot.Bind<A>().ToSelf().InSingletonScope();
You got yourself a major problem. There's two scenarios how this can pan out:
You are trying to create an A outside of a request. It will fail. Instantiating the DbContext, ninject will look for HttpContext.Current - which is null at the time - and throw an Exception.
You are trying to create an A during a request. Instantiating will succeed. However, When you try to use some functionality of A (which is accessing DbContext in turn) after the request or during a new request, it will throw an ObjectDisposedException
To sum it up, an ObjectDisposedException when you access the DbContext can only be caused by two scenarios:
-you ar disposing the DbContext (or some component which in turn disposes the DbContext) before the request is over.
-you are keeping a reference to the DbContext (again, or to some component which in turn references the DbContext) across request boundaries.
That's it. Nothing complicated about this, but your object graph.
So what would help is drawing an object graph. Start from the root / request root. Then when you're done, start from the DbContext and check who's calling Dispose() on it. If there is no usage inside your code, it must be Ninject who's cleaning up when the request ends. That means, you need to check all references to the DbContext. Someone is keeping a reference across requests.
Original Answer:
You should look into scopes: https://github.com/ninject/ninject/wiki/Object-Scopes
Specifically, .InRequestScope() - or in case that is not appliccable to your problem - .InCallScope() should be interesting to you.
As you are already using .InRequestScope() for the original binding, i suggest that binding the shared context type also .InRequestScope() should be sufficient. It means every dependency of the OrderController will receive the same webinar context instance. Furthermore, if someone else in the same request wants to get a webinar context injected, he will also get the same instance.
You should look into scopes: https://github.com/ninject/ninject/wiki/Object-Scopes
Specifically, .InRequestScope() - or in case that is not appliccable to your problem - .InCallScope() should be interesting to you.
As you are already using .InRequestScope() for the original binding, i suggest that binding the shared context type also .InRequestScope() should be sufficient. It means every dependency of the OrderController will receive the same webinar context instance. Furthermore, if someone else in the same request wants to get a webinar context injected, he will also get the same instance.
Related
In my netcoreapp3.1 project I got the error just from one request I've recently added to my project: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext.
I couldn't find solution because I wrote await before every async request and my db context is transient.
My Db Context:
services.AddDbContext<MyContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
},
ServiceLifetime.Transient
);
And this is where I got the error:
public class ProductDataRepository : UpdatableRepository<PRODUCT_DATA>, IProductDataDAL
{
private readonly MyContext _context;
public ProductDataRepository(MyContext context) : base(context)
{
_context = context;
}
public async Task<PRODUCT_DATA> GetProductById(string productId)
{
return await _context.PRODUCT_DATA.AsNoTracking().FirstOrDefaultAsync(pd => pd.PRODUCTID == productId);
}
public async Task<bool> IsProductMeltable(string productId)
{
// here is where I got the error
return await _context.MELTABLE_PRODUCTS.AsNoTracking().AnyAsync(x => x.PRODUCTID.Equals(productId));
}
}
And my DI:
services.AddScoped<IProductDataDAL, ProductDataRepository>();
services.AddScoped<IProductDataService, ProductDataManager>();
In manager:
public Task<bool> IsProductMeltable(string productId)
{
return await _productDataDAL.IsProductMeltable(productId);
}
In controller:
myModel.IS_MELTABLE = await _commonService.ProductDataService.IsProductMeltable(productData.PRODUCTID);
I also changed my methods from async to sync but still got the same error.
Thanks for your help in advance
Without seeing all the places that these methods might be called, it is difficult to find the source.
But, two things that may help:
The error reported does indicate that the same context is being called multiple times.
Transient means that the DI container will provide a brand new instance each time one is requested.
Regarding that second point, be aware that you are injecting it into a 'scoped' service.
So, this means that whilst your context is 'transient' that does not mean a brand new context is provided each time it is called. It means that a new one is requested each time a context is requested.
As your other services are 'scoped' this means that they only request a context once per request scope. So, even though your context is registered as transient, the SAME context will be used throughout the lifetime of a scoped service that requests it.
I notice that your calling your repository from different layers. One from controller and one from a manager service. This is likely to cause challenges.
Try to keep each layer having different responsibilities.
Best to use the controller as a very thin layer to simply receive HttpRequests and immediately pass responsibilty over to a service layer to do business logic and interact with repositories.
Cleaning that up a little may help you identify the problem.
I wrote a test that checks that every ...Configuration class in my project (I have several of them) is resolved when I call serviceProvider.GetRequiredService. The test works, but I don't understand why it works also when I remove the registration for a class: I just need to register a single configuration. For example:
[Fact]
public void Test()
{
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("./Configurations/appsettings1.json");
var services = new ServiceCollection();
//services.Configure<ActorSystemConfiguration>(configuration.GetSection("ActorSystem"));
var serviceProvider = services.BuildServiceProvider();
var configurationSection = serviceProvider.GetRequiredService<IOptionsMonitor<ElasticSearchConfiguration>>();
Assert.Equal("elasticsearchserverurl", configurationSection.CurrentValue.ServerUrl);
}
if I run the test in this way, without any call to services.Configure, then I get the expected behaviour with a "No service for type 'Microsoft.Extensions.Options.IOptionsMonitor`1[TNW.Server.Configurations.ElasticSearchConfiguration]' has been registered." exception.
If I remove the comment on the line about services.Configure (note: about another configuration section!) then I always get an empty but not null configuration object.
From the documentation GetRequiredService should throw the exception and GetService should return a null object.
I want to understand what is going on in order to be sure that I am not doing anything wrong and eventually I don't need to care about missing registrations here.
My ASP.NET MVC 4 project is using NHibernate (behind repositories) and Castle Windsor, using the AutoTx and NHibernate Facilities. I've followed the guide written by haf and my I can create and read objects.
My PersistenceInstaller looks like this
public class PersistenceInstaller : IWindsorInstaller
{
public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
{
container.AddFacility<AutoTxFacility>();
container.Register(Component.For<INHibernateInstaller>().ImplementedBy<NHibernateInstaller>().LifeStyle.Singleton);
container.AddFacility<NHibernateFacility>(
f => f.DefaultLifeStyle = DefaultSessionLifeStyleOption.SessionPerWebRequest);
}
}
The NHibernateInstaller is straight from the NHib Facility Quickstart.
I am using ISessionManager in my base repository...
protected ISession Session
{
get
{
return _sessionManager.OpenSession();
}
}
public virtual T Commit(T entity)
{
Session.SaveOrUpdate(entity);
return entity;
}
Finally, my application code which is causing the problem:
[HttpPost]
[ValidateAntiForgeryToken]
[Transaction]
public ActionResult Maintain(PrescriberMaintainViewModel viewModel)
{
if (ModelState.IsValid)
{
var prescriber = UserRepository.GetPrescriber(User.Identity.Name);
//var prescriber = new Prescriber { DateJoined = DateTime.Today, Username = "Test" };
prescriber.SecurityQuestion = viewModel.SecurityQuestion;
prescriber.SecurityAnswer = viewModel.SecurityAnswer;
prescriber.EmailAddress = viewModel.Email;
prescriber.FirstName = viewModel.FirstName;
prescriber.LastName = viewModel.LastName;
prescriber.Address = new Address
{
Address1 = viewModel.AddressLine1,
Address2 = viewModel.AddressLine2,
Address3 = viewModel.AddressLine3,
Suburb = viewModel.Suburb,
State = viewModel.State,
Postcode = viewModel.Postcode,
Country = string.Empty
};
prescriber.MobileNumber = viewModel.MobileNumber;
prescriber.PhoneNumber = viewModel.PhoneNumber;
prescriber.DateOfBirth = viewModel.DateOfBirth;
prescriber.AHPRANumber = viewModel.AhpraNumber;
prescriber.ClinicName = viewModel.ClinicName;
prescriber.ClinicWebUrl = viewModel.ClinicWebUrl;
prescriber.Qualifications = viewModel.Qualifications;
prescriber.JobTitle = viewModel.JobTitle;
UserRepository.Commit(prescriber);
}
return View(viewModel);
}
The above code will save a new prescriber (tested by uncommenting out the commented out line etc).
I am using NHProf and have confirmed that no sql is sent to the database for the Update. I can see the read being performed but that's it.
It seems to me that NHibernate doesn't recognise the entity as being changed and therefore does not generate the sql. Or possibly the transaction isn't being committed?
I've been scouring the webs for a few hours now trying to work this one out and as a last act of desperation have posted on SO. Any ideas? :)
Oh and in NHProf I see three Sessions (1 for the GetPrescriber call from the repo, one I assume for the update (with no sql) - and one for some action in my actionfilter on the base class). I also get an alert about the use of implicit transactions. This confuses me because I thought I was doing everything I needed to get an transaction - using AutoTx and the Transaction attribute. I also expected there to be only 1 session per webrequest, as per my Windsor config.
UPDATE: It seems, after spending the day reading through the source for NHibernateFacility and AutoTx Facility for automatic transactions, that AutoTx is not setting the Interceptors on my implementation of INHibernateInstaller. It seems this means whenever SessionManager calls OpenSession it is calling the default version with no parameter, rather than the one that accepts an Interceptor. Internally AutoTxFacility registers TransactionInterceptor with windsor, so that it can be added the Interceptor on my INHibernateInstaller concrete, by windsor making use of the AutoTx's TransactionalComponentInspector
AutoTxFacility source on github
To me it looks like creating sessions for every call to the repository. A session should span the whole business operation. It should be opened at the beginning and committed and disposed at the end.
There are other strange things in this code.
Commit is a completely different concept than SaveOrUpdate.
And you don't need to tell NH to store changes anyway. You don't need to call session.Save for objects that are already in the session. They are stored anyway. You only need to call session.Save when you add new objects.
Make sure that you use a transaction for the whole business operation.
There is one most likely "unintended" part in the code snippet above. And proven by observation made by NHProf
Oh and in NHProf I see three Sessions (1 for the GetPrescriber call
from the repo, one I assume for the update (with no sql) - and one for
some action in my actionfilter on the base class).
Calling the OpenSession() is triggering creation of a new session instances.
protected ISession Session
{
get { return _sessionManager.OpenSession(); }
}
So, whenever the code is accessing the Session property, behind is new session instance created (again and again). One session for get, one for udpate, one for filter...
As we can see here, the session returned by SessionManager.OpenSession() must be used for the whole scope (Unit of work, web request...)
http://docs.castleproject.org/Windsor.NHibernate-Facility.ashx
The syntaxh which we need, si to create one session (when firstly accessed) and reuse it until enf of scope (then later correctly close it, commit or rollback transaction...). Anyhow, first thing right now is to change the Session property this way:
ISession _session;
protected ISession Session
{
get
{
if (_session == null)
{
_session = sessionFactory.OpenSession();
}
return _session;
}
}
After spending a full day yesterday searching through the AutoTx and NHibernate facilities on github and getting nowhere, I started a clean project in an attempt to replicate the problem. Unfortunately for the replication, everything worked! I ran Update-Package on my source and brought down new version of Castle.Transactions and I was running correctly. I did make a small adjustment to my own code. That was to remove the UserRepository.Commit line.
I did not need to modify how I opened sessions. That was taken care of by the SessionManager instance. With the update to Castle.Transactions, the Transaction attribute is being recognised and a transaction is being created (as evidenced by no more alert in NHProf).
I have the following bootstrap
public class NancyBootStrapper: DefaultNancyBootstrapper
{
protected override void ConfigureRequestContainer(TinyIoC.TinyIoCContainer container, NancyContext context)
{
base.ConfigureRequestContainer(container, context);
var ravenSession = container.Resolve< IRavenSessionProvider >().GetSession();
container.Register( ravenSession );
}
}
When my Nancy app tries to instantiate BlogService using the following constructor
public BlogService(IDocumentSession documentSession)
{
this.documentSession = documentSession;
}
the application blows up stating that it can't resolve document session, I have also tried the following within my test method (removing the constructor injection).
public void BuildCategories()
{
var container = TinyIoCContainer.Current;
documentSession = container.Resolve< IDocumentSession >();
documentSession.Store(new Category{Title = "test"});
documentSession.Store(new Category{Title = ".net"});
documentSession.SaveChanges();
}
This also blows up, pointing out that it can't resolve documentSession.
Now this is the first time I have used either NancyFX or TinyIoC so I could be doing something fundamentally wrong though I should mention that the documentSession does resolve within a Nancy module..
Can any one offer a fix or some suggestions?
When is the BlogService supposed to be instantiated? -My guess would be once for the application, in which case I believe you are registering the session in the wrong bootstrapper method, and should do it in ConfigureApplicationContainer.
I've been playing & digging into both NancyFx and the TinyIoC code bases and have figured out how to fix this issue... I don't like the fix... but hay it works :)
Basically, I am creating a RavenDB document session in the bootstrapper method configureRequestContainer as it is best practice to use the request as your unit of work scope.
Unfortunately anything that is auto wired by tinyIoC within configureApplicationContainer does not have any constructor injection performed using the child container being used by the Nancy request (this includes those that are marked as MultiInstance or as PerRequestSingleton.
To get around this, you need to re-register any components that depend on your per request components within the same child container.
As I said, I don't like the fix, but it is ultimately a fix :)
About the declaration of ISession.
Should we close the Session everytime we use it, or should we keep it open?
I'm asking this because in manual of NHibernate (nhforge.org) they recommend us to declare it once in Application_Start for example, but i don't know if we should close it everytime we use.
Thanks
You can keep one single static reference to a ISessionFactory, which can be indeed instantiated in Application_Start for web applications.
However, ISession must not be kept open and cannot be shared between two or more requests. You should adopt the "one session per request" pattern which allows you to build a single ISession for each HTTP request and dispose it safely once the request has been handled (this is assuming you are writing a web application).
For instance, the code handling NHibernate sessions in you project might look like this:
public static class NHibernateHelper {
static ISessionFactory _factory;
public static NHibernateHelper(){
//This code runs once when the application starts
//Use whatever is needed to build your ISessionFactory (read configuration, etc.)
_factory = CreateYourSessionFactory();
}
const string SessionKey = "NhibernateSessionPerRequest";
public static ISession OpenSession(){
var context = HttpContext.Current;
//Check whether there is an already open ISession for this request
if(context != null && context.Items.ContainsKey(SessionKey)){
//Return the open ISession
return (ISession)context.Items[SessionKey];
}
else{
//Create a new ISession and store it in HttpContext
var newSession = _factory.OpenSession();
if(context != null)
context.Items[SessionKey] = newSession;
return newSession;
}
}
}
This code is probably far to simple and has not been tested (nor compiled in fact), but it should work. For a more safe handling of your sessions you could also use an IoC container (Autofac for instance) and register your ISessions with a lifetime that depends on HTTP requests (Autofac will handle everything for you in that case).
Sessions should be closed when you are done with them. There are multiple possible ways to manage the lifetime of a session and choosing the right one is specific to each scenario. "Unit of Work" and "Session per Request" are the two most often used session lifetime management patterns.
In Application_Start, you should create the SessionFactory, not the Session. The SessionFactory does not need to be closed.