EF,ASP MVC +依赖注入。多个并发请求和数据库连接的问题

时间:2014-11-12 11:55:24

标签: asp.net-mvc entity-framework dependency-injection autofac nopcommerce

我正在开发一个基于NopCommerce的项目,该项目使用ASP MVC,Autofac和Entity Framework。我在从MVC Route中调用服务上的方法时会发生异常,这将使用EF调用DB。

在开发期间,一切正常 - 但是在负载测试期间,当有并发用户时,1或2个请求将崩溃,并且以下错误之一将记录到ELMAH。

  

System.InvalidOperationException ExecuteReader需要一个开放且可用的连接。连接的当前状态是打开的。

     

System.InvalidOperationException ExecuteReader需要一个开放且可用的连接。连接的当前状态已关闭。

     

System.InvalidOperationException 连接未关闭。连接的当前状态是连接。

     

System.ObjectDisposedException ObjectContext实例已被释放,不能再用于需要连接的操作。

     

System.ObjectDisposedException 由于已经处理了DbContext,因此无法完成操作。

     

System.ArgumentException 已添加具有相同键的项目。

我通过在网站上打开许多链接,然后使用Chrome插件刷新所有标签来模拟同时点击网站的~25个请求来测试。

该服务有两个从路径内部调用的方法,然后这些相同方法中的一个可以从控制器动作调用50次以上。有时异常是从Route内部触发的,有时它来自控制器内部。意味着路径的GetRouteData已经完成,将'flow'传递给控制器​​,然后在那里失败。但是,大多数情况下,路由中会发生异常。当异常确实从控制器内部发生时,它位于异常发生的不同行。

有时一个方法会失败,另一个方法运行正常,然后调用堆栈中的下一个方法失败。它每次都不同,但这些方法调用使用常用方法从数据库中检索。

在此之前注册的其他2个路由映射到* { url} ,这些路由执行传入URL的数据库查找,并且异常从未发生过。因此,这种路由不是执行任何数据库工作的第一个操作。

该服务是依赖注册的: -

builder.RegisterControllers(typeFinder.GetAssemblies().ToArray());

builder.Register<IDbContext>(c => new NopObjectContext(DataSettings.DataConnectionString)).InstancePerLifetimeScope();

builder.RegisterGeneric(typeof(EfRepository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();

builder.RegisterType<MemoryCacheManager>().As<ICacheManager>().Named<ICacheManager>("nop_cache_static").SingleInstance();

builder.RegisterType<MyService>().As<IMySerivce>()    
               .WithParameter(ResolvedParameter.ForNamed<ICacheManager>("nop_cache_static"))    
               .InstancePerLifetimeScope(); 

Controller通过构造函数注入接收服务: -

protected readonly IMyService _myService;

public MyController(IMyService myService)
{
    _myService = myService;
}

路由解析服务如下:

public override RouteData GetRouteData(HttpContextBase httpContext)
{
    var _myService = EngineContext.Current.Resolve<IMyService>();
    myService.databaseOperation(); <--- falls over here w/ concurrency
} 

为什么会出现这些错误?以及如何解决它们?

根据我的理解,我们的DBContext似乎是在两个请求之间共享的,但是在我的依赖注册中,我告诉它要解析为一个生命周期得分 - 这对于每个请求都应该是唯一的。我已经阅读了很多关于这个异常的原因以及它如何依赖于依赖注入框架来控制依赖关系的生命周期以及管理其资源的处理 - 这就是事情正在崩溃,似乎。

关于服务本身,它的工作方式与应用程序中的所有其他服务一样 - 没有什么特别突出和不同的。

我已经粘贴了完整的异常堆栈跟踪http://pastebin.com/XYEwRQsv以及有问题的代码行。

编辑:我在连接字符串中使用MultipleActiveResultSets = True。该服务处理的实体是独立的,即它与其他实体没有关系,因此一旦执行了查询,就不应该有多个子实体迭代,因为有关这些异常的其他答案指出了。

编辑:这是抛出异常的行。

public string GetFriendlyUrlString(string originalUrl)
{
    var friendly =  _cacheManager.Get(originalUrl.ToLower(), () =>
            (from f in _friendlyUrlRepository.ReadOnlyTable      <--------- Here
             where f.OriginalUrl == originalUrl
             select f.Url).ToList().SingleOrDefault());

    return friendly ?? originalUrl;
}

例外是

ExecuteReader requires an open and available Connection. The connection's current state is closed.

这太奇怪了。在我的路线中,有4个地方可以进行数据库调用。我的例外是95%的时间来自这4次呼叫中的一次 - 通常是第一次失败,但有时第一次数据库呼叫将是正常的,而其他呼叫将通过。我很少看到异常来自控制器内部,这条路线呃...路线到。再次,使用该控制器异常,然后实际的数据库连接问题发生在5行代码中的一行 - 再次表明它已经使许多数据库调用失败了。

4 个答案:

答案 0 :(得分:4)

Autofac的InstancePerLifetimeScope不保证每个http请求的唯一范围。它保证的是,在解析它的生命周期范围内只有一个组件实例。 因此,例如,如果从根容器中解析InstancePerLifetimeScope组件,则该组件在应用程序的过程中将基本上充当单例。

如果您正在单独注册的服务(例如,全局操作过滤器)中解析您的依赖项,那么您的依赖项(dbContext或其他)将不会在每个请求上处理 - 它将保持打开,泄漏记忆和流血连接,直到它被处理掉或取消你的应用程序。

假设您正在使用Autofac Mvc集成,作为一个实验,也许尝试TEMPORARILY将您的dbContext注册为InstancePerMatchingLifetimeScope(“AutofacWebRequest”) - 这实际上相当于完成Will Appleby所说的,但没有亲和力比您可能使用的更新版本的Autofac。

如果这解决了你的问题,那么基本上,@ Apple Appleby是对的。您需要将所有InstancePerLifetimeScope组件注册为InstancePerRequest(从版本3.4.0开始新添加到Autofac核心)或InstancePerHttpRequest(在3.4.0之前的Autofac MVC集成中可用)。

否则,如果我猜对了,你可能会得到一个像这样的例外:

DependencyResolutionException: No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested.

如果发生这种情况,您仍然希望将注册更改为InstancePerRequest / InstancePerHttpRequest,但现在您手头有了更深入的任务。你可能有一个单独的(或其他比httpRequest更长的对象)持有你的dbContext人质。您需要确定更长寿命的组件,并弄清楚如何释放该自组织依赖 - 这可能需要重构组件以更好地遵守Autofac的范围行为。

您需要参考此处的问题排查部分: http://autofac.readthedocs.org/en/latest/faq/per-request-scope.html#troubleshooting-per-request-dependencies

以下是摘录:

  

此常见原因包括:

     
      
  • 应用程序注册正在跨应用程序类型共享。
  •   
  • 单元测试正在运行实际的应用程序注册,但不是模拟每个请求的生命周期。
  •   
  • 您的组件的生命周期超过一个请求,但它只依赖于一个请求。例如,一个单件组件,它接受按请求注册的服务。
  •   
  • 代码在应用程序启动期间运行(例如,在ASP.NET Global.asax中),在没有活动请求时使用依赖项解析。
  •   
  • 代码在“后台线程”中运行(没有请求语义)但是正在尝试调用ASP.NET MVC DependencyResolver来执行服务定位。
  •   

答案 1 :(得分:2)

您确定您的IDbContext实现正在注入正确的范围吗?我自己不使用Autofac,但快速检查他们的网站文档表明InstancePerRequest将更适合您的需求:

InstancePerRequest

你的连接字符串中是否有MARS设置为true?

Multiple Active Result Sets

答案 2 :(得分:2)

最后的修复归结于我在Route类中有一个私有变量。我没有意识到每次请求都没有实例化Route对象,而且每次在&#34; GetRouteData&#34;内部我都会初始化这个私有变量。因此,在对站点的2个并发请求期间,请求A将实例化变量并继续执行其方法,而请求B进入时,重新设置私有变量,这会使请求A的所有内容混乱。

我现在已经将这个变量本地化,并将其传递给任何其他需要它的方法,我的所有问题都消失了。所以,它并不是真正的Autofac,也不是实体框架 - 我还没有考虑过MVC如何对待它的路线。

感谢所有人的帮助,并正确地指出问题所在。

答案 3 :(得分:1)

您的代码基本上是正确的。您显示的依赖项来自nopCommerce本身(IDbContext完全相同,但我想您已将其删除一点以在此处显示)并且您的服务注册是标准的。有更多的数据库访问路由在nopcommerce中没有失败;如果是这种情况会有很多错误报告。

这让我认为问题应该在其他地方,所以我在没有发现任何问题的情况下进行了同样的测试。

我自己从未使用过Glimpse,但我已经在你的堆栈跟踪中看到了它。你没有Glimpse进行过一些测试吗?我看到它使用Castle来生成代理,这可能会搞乱你的依赖...问题必须在nopCommerce代码之外的其他地方,可能在你的代码或工具中。

顺便说一句。我看到你正在使用nopcommerce 3.30或3.40。由于不再需要v3.40 MARS,导致性能提升......