Session is closed Object name: 'ISession'. at NHibernate.Impl.AbstractSessionImpl.ErrorIfClosed() - How to stop the session from closing prematurely
Asked Answered
A

2

6

I am using NHibernate in an MVC C# application with MySQL. I am trying to have multiple users access the session. I have been using .InRequestScope() on my session but i am still getting:

System.ObjectDisposedException: Session is closed! Object name: 'ISession'. at NHibernate.Impl.AbstractSessionImpl.ErrorIfClosed() *

...or DataReader errors when i have my colleagues all navigate to the same page that accesses a Service at the same time.

My IMasterSessionSource injection

Bind<IMasterSessionSource>().To<GeneralMasterSessionSource()
                            .InRequestScope();

My IContentService is where my mappings are getting serviced

        //ContentService Bingings
        Bind<IContentService>().To<ContentService>().InRequestScope();
        Bind<ISession>()
            .ToMethod(
                context =>
                    context.Kernel.Get<IMasterSessionSource>()
                        .ExposeConfiguration()
                        .BuildSessionFactory()
                        .OpenSession()
            )
            .WhenInjectedInto<IContentService>()
            .InRequestScope();

ContentService

public interface IContentService
    {
        IQueryable<Question> Questions{ get; }
    }


 public class ContentService : IContentService
    {
        private readonly ISession _session;

        public ContentService(ISession session)
        {
            _session = session;
        }

        public IQueryable<Question> Questions
        {
            get { return _session.Query<Question>(); }
        }
    }

DetailsService

 public interface IDetailsService
    {
        IEnumerable<Question> PullQuestions();
    }

 public class DetailsService : IDetailsService
    {
        private readonly IContentService _contentService;


        public GeneralService(IContentService contentService)
        {
            _contentService = contentService;
        }

        public IEnumerable<Question> PullQuestions()
        {
            var result = _contentService.Questions;
            return result;
        }
}

CONTROLLER

public class Test: Controller
    {

        private readonly IContentService _contentService;
        private readonly IGeneralService _generalService;

        public CollegeController(IContentService contentService, IDetailsService detailsService)
        {
            _contentService = contentService;
            _detailsService = detailsService;
        }

        public ActionResult Index()
        {
            {
                var model = new HomePageContent
                {
                    Questions = _detailsService.PullQuestions().ToList();
                };
            }
        }
    }

MODEL

 public class HomePageContent
    {
        public IEnumerable<Question> Questions { get; set; }
    }

VIEW

foreach(var question in Model.Questions){
@Html.Raw(question.Question)
}

So for a single user visiting that page. All works fine. But when mutliple users visist the same page each get the errors:

{"There is already an open DataReader associated with this Connection which must be closed first."} {"There is already an open DataReader associated with this Connection which must be closed first."} {"No current query in data reader"} {"No current query in data reader"} {"There is already an open DataReader associated with this Connection which must be closed first."} {"Session is closed!\r\nObject name: 'ISession'."}

I already added InRequestScope. I even added this implementation: NHibernate, and odd "Session is Closed!" errors

but I am still getting Sessions are closed! errors. I even tried to create a new Kernel.Get if the session was closed, but the problem is that the error sometimes occurs even when the session is open. Please help! I am at wits end with this issue, and I can't seem to find the solution anywhere. I almost think it's impossible for NHibernate to handle more than one session at once.

UPDATE

Maybe there's a way to wait for disposed session before opening new?

Stack Trace

[ObjectDisposedException: Session is closed! Object name: 'ISession'.] NHibernate.Impl.AbstractSessionImpl.ErrorIfClosed() +192
NHibernate.Impl.AbstractSessionImpl.CheckAndUpdateSessionStatus() +55 NHibernate.Impl.AbstractSessionImpl.CreateQuery(IQueryExpression queryExpression) +171
NHibernate.Linq.DefaultQueryProvider.PrepareQuery(Expression expression, IQuery& query, NhLinqExpression& nhQuery) +226
NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression) +80 NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression) +74 Remotion.Linq.QueryableBase1.GetEnumerator() +193 System.Collections.Generic.List1..ctor(IEnumerable1 collection) +432 System.Linq.Enumerable.ToList(IEnumerable1 source) +70
Gcus.PublicGeneralSite.Data.Core.Service.General.DetailsService.FindItems(String item, String controller) in c:\Users\wd\Desktop\master\Gcus.PublicGeneralSite.Data.Core\Service\General\DetailsService.cs:724 Gcus.Com.Web.Controllers.CoursesController.Details(String category, String item) in c:\Users\wd\Desktop\master\Gcus.Com.Web\Controllers\CoursesController.cs:213 lambda_method(Closure , ControllerBase , Object[] ) +366
System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +87
System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary2 parameters) +603
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary
2 parameters) +93
System.Web.Mvc.Async.ActionInvocation.InvokeSynchronousActionMethod() +97 System.Web.Mvc.Async.AsyncControllerActionInvoker.b__39(IAsyncResult asyncResult, ActionInvocation innerInvokeState) +53
System.Web.Mvc.Async.WrappedAsyncResult2.CallEndDelegate(IAsyncResult asyncResult) +137
System.Web.Mvc.Async.WrappedAsyncResultBase
1.End() +187
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +136
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) +76
System.Web.Mvc.Async.AsyncInvocationWithFilters.b__3d() +164 System.Web.Mvc.Async.<>c__DisplayClass46.b__3f() +549 System.Web.Mvc.Async.<>c__DisplayClass33.b__32(IAsyncResult asyncResult) +75
System.Web.Mvc.Async.WrappedAsyncResult1.CallEndDelegate(IAsyncResult asyncResult) +79
System.Web.Mvc.Async.WrappedAsyncResultBase
1.End() +187
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +136
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +76
System.Web.Mvc.Async.<>c__DisplayClass2b.b__1c() +114 System.Web.Mvc.Async.<>c__DisplayClass21.b__1e(IAsyncResult asyncResult) +306
System.Web.Mvc.Async.WrappedAsyncResult1.CallEndDelegate(IAsyncResult asyncResult) +75
System.Web.Mvc.Async.WrappedAsyncResultBase
1.End() +176
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +72
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +60
System.Web.Mvc.Controller.b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState) +70
System.Web.Mvc.Async.WrappedAsyncVoid1.CallEndDelegate(IAsyncResult asyncResult) +135
System.Web.Mvc.Async.WrappedAsyncResultBase
1.End() +176
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +72
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +51
System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +66 System.Web.Mvc.Controller.b__15(IAsyncResult asyncResult, Controller controller) +60
System.Web.Mvc.Async.WrappedAsyncVoid1.CallEndDelegate(IAsyncResult asyncResult) +98
System.Web.Mvc.Async.WrappedAsyncResultBase
1.End() +176
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +72
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +51 System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +60
System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +60
System.Web.Mvc.MvcHandler.b__5(IAsyncResult asyncResult, ProcessRequestState innerState) +70
System.Web.Mvc.Async.WrappedAsyncVoid1.CallEndDelegate(IAsyncResult asyncResult) +135
System.Web.Mvc.Async.WrappedAsyncResultBase
1.End() +176
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +72
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +51
System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +60 System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +59
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +399 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +137

Apteryx answered 26/7, 2016 at 18:6 Comment(13)
NHibernate certainly have no problem with multiple concurrent sessions. In fact, it's designed to handle precisely that scenario. It cannot however handle concurrent access to a single session. The session is not threadsafe. You do seem to be aware of this, since you speak about InRequestScope() etc, which sounds like it should do the trick. It seems this is more of a problem with ninject (or how you use it) than NHibernate.Brahms
Maybe you haven't set up NInject properly for MVC? #24928570Brahms
i have it set up properly. I'm using NInject.MVC5 and injecting them into the correct controllers. The problem seem to be when there's two requests for the same session. Somehow one maybe has to wait till connection closed. But i'm not sure. I just cant believe this is so difficult.Apteryx
Let me repeat this: NHibernate is designed to handle multiple concurrent threads, sessions and connections. Bugs do happen, but this is very unlikely to be a problem in NHibernate. However, the NHibernate rule (simply put) that must be followed is that a single session instance AND any data loaded from the session must be used from ONLY 1 thread at a time.Brahms
So this is not about "waiting for a session to be disposed" or something like that. Multiple sessions are fine. The solution to the problem lies elsewhere.Brahms
Can we see some stacktraces from the exceptions you're getting? Are you sure that you are not accidentally sharing some loaded data between different requests?Brahms
@Oskar Berggen - I added a stack trace in the update. I really appreciate your feedback. I am not sure if its sharing loaded data. I think maybe MySQL is closing the session. But i have no idea.Apteryx
Well, the stack trace mentions DetailsService - is that setup the same way as in the code you show? There is also a lot about "async" in the stack trace but I'm afraid it's difficult to understand how all that relates to NInject's InRequestScope(). Are you in anyway moving sessions around yourself? Or opening/closing/disposing them outside ninject?Brahms
@OskarBerggren - In my example i put GeneralService, but DetailService is roughly the same type of service. A service that handles session queries. I updated the question to reflect DetailService. I don't know why its doing an async wrapper. But i don't think that's related to the main problem with Sessions.Apteryx
Unless the problem is in the ninject setup (which I'm not familiar with) there is simply not enough information here to be able to understand the issue.Brahms
well it just seems like ISession might be Shared. I think i need to figure out how to have multiple sessions per request. :( i definately need help with how to do that. Cant find answers anywhere on examples.Apteryx
Are you using System.Transactions/ambient transations, or ONLY session.BeginTransaction()?Brahms
for the normal queries i am using the ContentService seen above. but for writing and updating i am using BeginTransaction() but my error is only when a session is using a connect and another session cannot access that connection. or when the connection closes on another session.Apteryx
A
2

I finally figured it out. Much appreciation to @Oskar Berggren for at least being avid on understanding my dilemma.

The problem was that I, in fact, was sharing one session all along.

here is where i am binding an open session to ContentService:

 Bind<IContentService>().To<ContentService>().InRequestScope();
        Bind<ISession>()
            .ToMethod(
                context =>
                    context.Kernel.Get<IMasterSessionSource>()
                        .ExposeConfiguration()
                        .BuildSessionFactory()
                        .OpenSession()
            )
            .WhenInjectedInto<IContentService>()
            .InRequestScope(); 

Here is where i am calling the same session in ContentService

 public class ContentService : IContentService
    {
        private readonly ISession _session;

        public ContentService(ISession session)
        {
            _session = session;
        }

        public IQueryable<Question> Questions
        {
            get { return _session.Query<Question>(); }
        }
    }

Here is where the problem is. I am calling the SAME session in another service that is being used elsewhere

public class DetailsService : IDetailsService
    {
        private readonly IContentService _contentService; //BAD


        public GeneralService(IContentService contentService)
        {
            _contentService = contentService; //BAD
        }

This is not thread safe, because that one open session is getting reused. Each service should have its own session.

so i created binding for DetailService with its own session like so....

 Bind<IDetailsService>()
                .To<DetailsService>()
                .InRequestScope();

            Bind<ISession>()
                .ToMethod(
                    context =>
                    {
                        var lockObject = new object();

                        lock (lockObject)
                        {
                            return context.Kernel.Get<IMasterSessionSource>()
                                .ExposeConfiguration()
                                .BuildSessionFactory()
                                .OpenSession();
                        }
                    }
                )
                .WhenInjectedInto<IDetailsService>()
                .InRequestScope();

And instead of calling _contentService in that service i added the session to its constrictor

    private readonly ISession Session;

    public DetailsService(ISession session)
    {
        Session = session;
    }

then just ran queries directly using Session.Query();

no more Session is closed errors, no more DataReader Errors, and finally a working product.

Apteryx answered 9/8, 2016 at 16:20 Comment(0)
B
0

I'm unfamiliar with ninject, which I suspect is involved in the problem here.

If you cannot figure out how to make that handle the sessions properly, you may have a look at NHibernate's own handling of contextual sessions: http://nhibernate.info/doc/nhibernate-reference/architecture.html#architecture-current-session

Brahms answered 27/7, 2016 at 20:57 Comment(1)
So it turns out that its not the sessions that's the problem... MySQL seems to close the session prematurely on other threads. But i still have no idea why and cannot find a fix for this anywhere.Apteryx

© 2022 - 2024 — McMap. All rights reserved.