EntityFramework DbContext生命周期+Postgres:"操作已在进行中。"

4
我最近一直在研究以下问题。
我有一个在Mono上运行的Nancy应用程序,使用实体框架、存储库模式和UnitOfWork,并使用Postgres。 Nancy使用TinyIoC作为它的IoC容器。
我有一个Web应用程序,将请求排队到前端,以便后端一次只处理一个请求。这一切都很好。
然而,当我运行连接到同一后端的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

我正在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...

}

我有一种感觉,这是一个多线程问题,两个单独的请求正在使用相同的DbContext(或底层连接),导致事情出错。
我已经尝试在Nancy引导程序的ConfigureRequestContainer方法中注册依赖项,但这会引发“连接未打开”异常。
这个问题背后的理论在这篇文章中清楚地解释了: http://mehdi.me/ambient-dbcontext-in-ef6/ 以下内容对我来说不太清楚:
  • 我是否正确地认为这是一个多线程问题?
  • 我需要知道确保每个请求使用其自己的DbContext/connection以避免冲突的正确方法,最好使用TinyIoC/Nancy来管理DbContext的生命周期。
我知道这是一个复杂的问题。如果您需要任何其他信息,请告诉我。
谢谢 :-)。

1
是的,很可能是单个 DbContext 被多个线程使用。在容器中注册 DbContext 作为每个请求的新实例,这样每次解析时都会创建一个新实例。 - Evk
我这样注册DbContext:container.Register<IReflectDbContext, ReflectDbContext> (); 如果我正确理解TinyIoC文档,这应该注册DbContext和多实例,并在每次解析时产生一个新实例。这是我目前设置的方式,也是导致上述情况的原因。所以这意味着即使它是多实例,这也不能保证每个请求都有一个新的实例? - Corstiaan
1
不,它默认会将接口注册为单例,因此每次返回相同的实例,这会导致您观察到的问题。请使用 container.Register<IMyInterface, MyConcreteType>().AsMultiInstance() 注册为多实例。 - Evk
你是指 ReflectDbContext 吗?你在哪里看到第二次出现了?我一定是瞎了... - Corstiaan
1
算了,我上一条评论读错了 :) 无论如何,使用这些更改应该会解决错误。如果还有问题,请检查是否在某种静态变量中存储了DbContext,或者您注册了另一个类作为单例(因此没有使用AsMultiInstance),并且此类在字段中保存了对DbContext的引用 - 这可能会导致多个线程重复使用单个DbContext实例。 - Evk
显示剩余3条评论
1个回答

1
我会为可能遇到同样错误的人扩展一下我的评论,以备将来参考。正如您可能已经知道的那样,Entity Framework的DbContext遵循所谓的“工作单元”模式,这意味着您必须为一个逻辑片段(单元)使用一个实例。不希望重复使用同一实例进行多个工作单元,并且在某些情况下,例如此类情况,甚至可能导致失败。与SQL Server不同,Postgresql不支持MARS(多个活动结果集),这意味着它不支持同时在同一连接上执行多个命令。当您从多个线程重用单个DbContext实例时,它们在执行其命令时重用相同的底层sql连接,从而导致上述错误。

正如评论中所述,解决问题的方法是始终为每个操作创建新的DbContext实例,并在之后处理它。这意味着将其注册为

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

请确保您永远不要将DbConext实例存储在静态字段\另一个类的单例实例中(例如,您的ReflectUnitOfWork是单例的,如果您在该字段中存储DbContext - 同样的问题再次出现)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接