EntityFramework DbContext lifecycle + Postgres: "Операция уже выполняется".

Я возился со следующим уже несколько дней.

У меня есть приложение Nancy, работающее на Mono, с EntityFramework с шаблоном Repository, UnitOfWork и Postgres. Нэнси использует TinyIoC в качестве контейнера IoC.

У меня есть веб-приложение, которое ставит запросы в очередь на внешнем интерфейсе, поэтому сервер получает один запрос за раз. Это все отлично работает.

Однако проблема начинается, когда я запускаю приложение iOS, которое подключается к одному и тому же внутреннему интерфейсу и не помещает запросы в очередь к внутреннему интерфейсу, а иногда запускает запрос почти одновременно.

Через случайные промежутки сервер начинает выдавать эту ошибку:

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

Я регистрирую зависимости как в загрузчике Нэнси:

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

}

У меня есть ощущение, что это проблема многопоточности и что два отдельных запроса используют один и тот же DbContext (или базовое соединение), что приводит к взрыву.

Я уже пытался зарегистрировать зависимости в ConfigureRequestContainer метод загрузчика Нэнси, но это исключение "Соединение не открыто".

Теория, лежащая в основе этой проблемы, четко объясняется в этой статье: http://mehdi.me/ambient-dbcontext-in-ef6/

Мне непонятно следующее:

  • Правильно ли я считаю, что это проблема многопоточности?
  • Мне нужно знать правильный способ убедиться, что каждый запрос использует свой собственный DbContext / connection, чтобы вещи не сталкивались, желательно используя TinyIoC/Nancy для управления жизненным циклом DbContext.

Я понимаю, что это сложная проблема. Дайте мне знать, если вам нужна дополнительная информация.

Спасибо:-).

1 ответ

Решение

Буду немного расширять мои комментарии для дальнейшего использования людьми, которые могут иметь такую ​​же ошибку. Как вы, наверное, уже знаете, Entity Framework DbContext следует так называемому шаблону "единицы работы", что означает, что вы должны использовать один экземпляр для одной логической части (единицы) работы. Повторное использование одного и того же экземпляра для нескольких единиц работы нежелательно, а в некоторых случаях подобное может привести к сбоям. В отличие от SQL Server, Postgresql не поддерживает MARS (множественные активные результирующие наборы), что означает, что он не поддерживает одновременное выполнение нескольких команд по одному и тому же соединению. При повторном использовании одного экземпляра DbContext из нескольких потоков они повторно используют одно и то же базовое соединение sql при выполнении своих команд, что приводит к указанной выше ошибке.

Как указано в комментариях, для решения проблемы всегда нужно создавать новый экземпляр DbContext за каждую операцию и утилизируйте ее потом. Это значит зарегистрировать его как

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

И убедитесь, что вы никогда не храните DbConext экземпляр в статическом поле \ экземпляр экземпляра другого класса (например, ваш ReflectUnitOfWork это синглтон, и если вы храните DbContext в поле - опять та же проблема).

Другие вопросы по тегам