EntityFramework DbContext lifecycle + Postgres: “A

2020-04-10 01:18发布

问题:

I have been messing with the following for a few days now.

I have a Nancy app running on Mono, with EntityFramework with Repository pattern and UnitOfWork, and Postgres. Nancy uses TinyIoC as it's IoC container.

I have a web app that queues requests on the front-end so the back-end gets hit one request at a time. This all works fine.

However, the trouble starts when I run an iOS app that connects to the same back-end and does not queue requests to the backend, firing request sometimes almost simultaneously.

At random intervals the backend starts throwing this error:

2016-09-20T13:30:16.120057436Z app[web.1]: System.Data.Entity.Core.EntityCommandExecutionException: An error occurred while executing the command definition. See the inner exception for details. ---> System.InvalidOperationException: An operation is already in progress.
2016-09-20T13:30:16.120104535Z app[web.1]:   at Npgsql.NpgsqlConnector.StartUserAction (ConnectorState newState) <0x41ad0150 + 0x00313> in <filename unknown>:0
2016-09-20T13:30:16.120113254Z app[web.1]:   at Npgsql.NpgsqlCommand.ExecuteDbDataReaderInternal (CommandBehavior behavior) <0x41acfe30 + 0x0002f> in <filename unknown>:0
2016-09-20T13:30:16.120119308Z app[web.1]:   at Npgsql.NpgsqlCommand.ExecuteDbDataReader (CommandBehavior behavior) <0x41acfe00 + 0x00013> in <filename unknown>:0
2016-09-20T13:30:16.120125313Z app[web.1]:   at System.Data.Common.DbCommand.ExecuteReader (CommandBehavior behavior) <0x41f1a3c0 + 0x00018> in <filename unknown>:0
2016-09-20T13:30:16.120131185Z app[web.1]:   at (wrapper remoting-invoke-with-check) System.Data.Common.DbCommand:ExecuteReader (System.Data.CommandBehavior)
2016-09-20T13:30:16.120206045Z app[web.1]:   at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.<Reader>b__c (System.Data.Common.DbCommand t, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext`1 c) <0x41f1ac20 + 0x00027> in <filename unknown>:0
2016-09-20T13:30:16.120220450Z app[web.1]:   at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1[TInterceptor].Dispatch[TTarget,TInterceptionContext,TResult] (System.Data.Entity.Infrastructure.Interception.TTarget target, System.Func`3 operation, System.Data.Entity.Infrastructure.Interception.TInterceptionContext interceptionContext, System.Action`3 executing, System.Action`3 executed) <0x41b1d3c0 + 0x0010e> in <filename unknown>:0
2016-09-20T13:30:16.120232740Z app[web.1]:   at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.Reader (System.Data.Common.DbCommand command, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext interceptionContext) <0x41f1a880 + 0x00263> in <filename unknown>:0
2016-09-20T13:30:16.120267802Z app[web.1]:   at System.Data.Entity.Internal.InterceptableDbCommand.ExecuteDbDataReader (CommandBehavior behavior) <0x41f1a3f0 + 0x000e6> in <filename unknown>:0
2016-09-20T13:30:16.120274613Z app[web.1]:   at System.Data.Common.DbCommand.ExecuteReader (CommandBehavior behavior) <0x41f1a3c0 + 0x00018> in <filename unknown>:0
2016-09-20T13:30:16.120318116Z app[web.1]:   at (wrapper remoting-invoke-with-check) System.Data.Common.DbCommand:ExecuteReader (System.Data.CommandBehavior)
2016-09-20T13:30:16.120326788Z app[web.1]:   at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.ExecuteStoreCommands (System.Data.Entity.Core.EntityClient.EntityCommand entityCommand, CommandBehavior behavior) <0x41f154c0 + 0x00043> in <filename unknown>:0
2016-09-20T13:30:16.120332587Z app[web.1]:   --- End of inner exception stack trace ---
2016-09-20T13:30:16.120336995Z app[web.1]:   at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.ExecuteStoreCommands (System.Data.Entity.Core.EntityClient.EntityCommand entityCommand, CommandBehavior behavior) <0x41f154c0 + 0x000b3> in <filename unknown>:0
2016-09-20T13:30:16.120344218Z app[web.1]:   at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlan.Execute[TResultType] (System.Data.Entity.Core.Objects.ObjectContext context, System.Data.Entity.Core.Objects.ObjectParameterCollection parameterValues) <0x41f11e50 + 0x000a4> in <filename unknown>:0

I am registering the dependencies like to, in the Nancy bootstrapper:

protected override void ConfigureApplicationContainer (TinyIoCContainer container)
        {
            base.ConfigureApplicationContainer (container);

            Database.SetInitializer<ReflectDbContext> (new NullDatabaseInitializer<ReflectDbContext> ()); // add this to allow prevent "The context cannot be used while the model is being created"

        container.Register<IReflectDbContext, ReflectDbContext> ();
        container.Register<ReflectUnitOfWork> ().AsSingleton ();

        container.Register<IReflectUserRepository, ReflectUserRepository> ();
        container.Register<IUserRepository<ReflectUser>, ReflectUserRepository> ();

        container.Register<IReviewRepository, ReviewRepository> ();

        container.Register<IReviewSetupRepository, ReviewSetupRepository> ();

        container.Register<IRepositoryV2<ReflectUserActivityItem>, EntityFrameworkRepository<ReflectUserActivityItem>> ();

        container.Register<IAuthenticationUnitOfWork<ReflectUser, ReflectUserActivityItem>, ReflectUnitOfWork> ();

        container.Register<IRepository<ReflectUserActivityItem>, NullRepository<ReflectUserActivityItem>> (); //TODO remove this when port is complete

        container.Register<IErrorLogger, SimpleLogLogger> ();
        container.Register<IGeoIpDataProvider, TelizeGeoIpDataProvider> ();
        container.Register<IRepository<ReviewSetup>, ServiceStackOrmLiteRepository<ReviewSetup>> ();
        container.Register<IEmailExporter, MailChimpUserEmailDataExporter> ();
        container.Register<IMailer, SmtpMailer> ();
        container.Register<IUserManager<ReflectUser>, UserManager<ReflectUser, ReflectUserActivityItem>> ();
        container.Register<IUserMessageManager<ReflectUser>, UserMessageManager<ReflectUser>> ();

etc...

}

I have a feeling that this is a multi-threading issue and that two separate requests are using the same DbContext (or underlying connection) which causes things to blow up.

I have already tried registering the dependencies in the ConfigureRequestContainer method of the Nancy bootstrapper but this throw a 'Connection is not open` exception.

The theory behind this issue is explained clearly in this article: http://mehdi.me/ambient-dbcontext-in-ef6/

The following is unclear to me:

  • Am I correct in assuming this is a multi-threading issue?
  • I need to know the proper way to make sure each request uses it's own DbContext/connection so stuff doesn't collide, perferably using TinyIoC/Nancy to manage the lifecycle of the DbContext.

I understand this is a complex issue. Let me know if you need any additional information.

Thank you :-).

回答1:

Will a bit expand my comments for future reference of people who might have the same error. As you probably know already, Entity Framework's DbContext follows so called "unit of work" pattern, which means you have to use one instance for one logical piece (unit) of work. Reusing the same instance for multiple units of work is not desired, and in some cases like this may even lead to failures. Unlike SQL Server, Postgresql does not support MARS (Multiple Active Result Sets), which means it does not support executing multiple commands over the same connection at the same time. When you reuse single instance of DbContext from multiple threads, they reusing the same underlying sql connection when executing their commands, which leads to the error above.

As stated in comments, the way to resolve the issue is always create new instance of DbContext for every operation, and dispose it afterwards. This means registering it as

container.Register<IReflectDbContext, ReflectDbContext> ().AsMultiInstance();

And ensure that you never store DbConext instance in a static field \ singleton instance of another class (for example your ReflectUnitOfWork is singleton, and if you store DbContext in a field there - same problem again).