4

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 :-).

Corstiaan
  • 1,114
  • 15
  • 34
  • 1
    Yes that's most likely single DbContext is used by multiple threads. Register DbContext in your container as new instance per request, so that new instance is created every time you resolve it. – Evk Sep 20 '16 at 13:56
  • I register the DbContext like so: `container.Register ();` If I understand the TinyIoC docs correctly this should register the DbContext and multi-instance, and yield a fresh instance every time it's resolved. This is how I have set it up at the moment and what causes the above situation. So this means that even though it's multi-instance, this does not guarantee a fresh instance every request? – Corstiaan Sep 20 '16 at 14:01
  • 1
    No it will register interfaces as singletons by default, so the same instance is returned every time, which causes the problem you observe. Do container.Register().AsMultiInstance() to register as multi-instance. – Evk Sep 20 '16 at 15:25
  • Ok. So now I am registering like `container.Register ().AsMultiInstance(); container.Register ();` but I am still seeing `System.InvalidOperationException: An operation is already in progress`. I understand this gives a new instance each time it's requested/resolved but does this also mean give each request get's it's own instance? Probably so but just to be sure there are no edge cases – Corstiaan Sep 20 '16 at 16:04
  • You seem to register it multiple times, thats just a typo? – Evk Sep 20 '16 at 16:12
  • You mean `ReflectDbContext`? Where do you see it a second time? I must be blind... – Corstiaan Sep 20 '16 at 16:47
  • 1
    Forget about it, I read last comment wrong :) Anyway, with this changes error should be gone. If it's not, check if you are not storing DbContext in some kind of static variable, or maybe you register _another_ class as singleton (so, without AsMultiInstance), and this class holds a reference to DbContext in a field - which might result in the same reuse of single DbContext instance by multiple threads – Evk Sep 20 '16 at 16:52
  • Ok. I will check. Thank you for your help. I already stumbled on some other possible causes due to you comments so you have been helpful none the less :-) – Corstiaan Sep 20 '16 at 16:53

1 Answers1

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

Evk
  • 98,527
  • 8
  • 141
  • 191