Search code examples
c#mysqlnhibernatefluent-nhibernateninject

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


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


Solution

  • 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.