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

8
  • 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. Commented Sep 20, 2016 at 13:56
  • I register the DbContext like so: container.Register<IReflectDbContext, ReflectDbContext> (); 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? Commented Sep 20, 2016 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<IMyInterface, MyConcreteType>().AsMultiInstance() to register as multi-instance. Commented Sep 20, 2016 at 15:25
  • Ok. So now I am registering like container.Register<IReflectDbContext, ReflectDbContext> ().AsMultiInstance(); container.Register<ReflectUnitOfWork> (); 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 Commented Sep 20, 2016 at 16:04
  • 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 Commented Sep 20, 2016 at 16:52

1 Answer 1

2

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.