C# 实体框架上下文的创建实例在负载下会变慢

C# 实体框架上下文的创建实例在负载下会变慢,c#,asp.net,performance,entity-framework,entity-framework-6,C#,Asp.net,Performance,Entity Framework,Entity Framework 6,我们注意到一些非常小的web服务调用花费的时间比我们预期的要长得多。我们做了一些调查并放置了一些计时器,我们将其缩小到创建实体框架DB6上下文的实例。不是查询本身,而是上下文的创建。此后,我做了一些日志记录,以查看创建DbContext实例实际需要的平均时间,似乎大约为50毫秒 应用程序预热后,上下文创建并不缓慢。在应用程序回收之后,它开始于2-4ms(这是我们在开发环境中看到的)。上下文的创建似乎随着时间的推移而减慢。在接下来的几个小时内,它将逐渐上升到50-80毫秒的范围并趋于平稳 我们的上

我们注意到一些非常小的web服务调用花费的时间比我们预期的要长得多。我们做了一些调查并放置了一些计时器,我们将其缩小到创建实体框架DB6上下文的实例。不是查询本身,而是上下文的创建。此后,我做了一些日志记录,以查看创建DbContext实例实际需要的平均时间,似乎大约为50毫秒

应用程序预热后,上下文创建并不缓慢。在应用程序回收之后,它开始于2-4ms(这是我们在开发环境中看到的)。上下文的创建似乎随着时间的推移而减慢。在接下来的几个小时内,它将逐渐上升到50-80毫秒的范围并趋于平稳

我们的上下文是一个相当大的代码优先上下文,大约有300个实体——包括一些实体之间相当复杂的关系。我们正在运行EF 6.1.3。我们正在执行“每个请求一个上下文”,但对于大多数web API调用,它只执行一个或两个查询。创建一个60毫秒以上的上下文,然后执行一个1ms的查询,这有点令人不满意。我们每分钟有大约10k个请求,所以我们不是一个容易使用的站点

这是我们所看到的情况的快照。时间是以毫秒为单位的,大倾角是一个回收应用程序域的部署。每行是4个不同web服务器中的一个。注意,它也不总是相同的服务器

我确实做了一次内存转储来尝试充实正在发生的事情,下面是堆的统计数据:

00007ffadddd1d60    70821      2266272 System.Reflection.Emit.GenericFieldInfo
00007ffae02e88a8    29885      2390800 System.Linq.Enumerable+WhereSelectListIterator`2[[NewRelic.Agent.Core.WireModels.MetricDataWireModel, NewRelic.Agent.Core],[System.Single, mscorlib]]
00007ffadda7c1a0     1462      2654992 System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.Object, mscorlib],[System.Object, mscorlib]][]
00007ffadd4eccf8    83298      2715168 System.RuntimeType[]
00007ffadd4e37c8    24667      2762704 System.Reflection.Emit.DynamicMethod
00007ffadd573180    30013      3121352 System.Web.Caching.CacheEntry
00007ffadd2dc5b8    35089      3348512 System.String[]
00007ffadd6734b8    35233      3382368 System.RuntimeMethodInfoStub
00007ffadddbf0a0    24667      3749384 System.Reflection.Emit.DynamicILGenerator
00007ffae04491d8    67611      4327104 System.Data.Entity.Core.Metadata.Edm.MetadataProperty
00007ffadd4edaf0    57264      4581120 System.Signature
00007ffadd4dfa18   204161      4899864 System.RuntimeMethodHandle
00007ffadd4ee2c0    41900      5028000 System.Reflection.RuntimeParameterInfo
00007ffae0c9e990    21560      5346880 System.Data.SqlClient._SqlMetaData
00007ffae0442398    79504      5724288 System.Data.Entity.Core.Metadata.Edm.TypeUsage
00007ffadd432898    88807      8685476 System.Int32[]
00007ffadd433868     9985      9560880 System.Collections.Hashtable+bucket[]
00007ffadd4e3160    92105     10315760 System.Reflection.RuntimeMethodInfo
00007ffadd266668   493622     11846928 System.Object
00007ffadd2dc770    33965     16336068 System.Char[]
00007ffadd26bff8   121618     17335832 System.Object[]
00007ffadd2df8c0   168529     68677312 System.Byte[]
00007ffadd2d4d08   581057    127721734 System.String
0000019cf59e37d0   166894    143731666      Free
Total 5529765 objects
Fragmented blocks larger than 0.5 MB:
            Addr     Size      Followed by
0000019ef63f2140    2.9MB 0000019ef66cfb40 Free
0000019f36614dc8    2.8MB 0000019f368d6670 System.Data.Entity.Core.Query.InternalTrees.SimpleColumnMap[]
0000019f764817f8    0.8MB 0000019f76550768 Free
0000019fb63a9ca8    0.6MB 0000019fb644eb38 System.Data.Entity.Core.Common.Utils.Set`1[[System.Data.Entity.Core.Metadata.Edm.EntitySet, EntityFramework]]
000001a0f6449328    0.7MB 000001a0f64f9b48 System.String
000001a0f65e35e8    0.5MB 000001a0f666e2a0 System.Collections.Hashtable+bucket[]
000001a1764e8ae0    0.7MB 000001a17659d050 System.RuntimeMethodHandle
000001a3b6430fd8    0.8MB 000001a3b6501aa0 Free
000001a4f62c05c8    0.7MB 000001a4f636e8a8 Free
000001a6762e2300    0.6MB 000001a676372c38 System.String
000001a7761b5650    0.6MB 000001a776259598 System.String
000001a8763c4bc0    2.3MB 000001a8766083a8 System.String
000001a876686f48    1.4MB 000001a8767f9178 System.String
000001a9f62adc90    0.7MB 000001a9f63653c0 System.String
000001aa362b8220    0.6MB 000001aa36358798 Free
这似乎是相当多的元数据和类型使用

我们尝试过的事情:

  • 创建要复制的简单测试线束。它失败了,我猜是因为我们没有改变流量,也没有改变查询运行的类型。仅仅加载上下文并反复执行几个查询并没有导致时间的增加
  • 我们已经大大减少了上下文,它是500个实体,现在是300个。在速度上没有什么不同。我猜是因为我们根本没有使用那200个实体
  • (编辑)我们使用SimpleInjector创建“每个请求的上下文”。为了验证它不是SimpleInjector,我已经通过在它中添加new来创建了一个上下文实例。同样的慢创建时间
  • (编辑)我们已经确定了EF。没有造成任何影响 我们可以进一步调查什么?我知道EF使用的缓存非常广泛,可以加快速度。缓存中的内容越多,上下文创建的速度就越慢?有没有一种方法可以准确地看到缓存中有什么东西,从而使里面的任何奇怪的东西变得更加真实?有人知道我们可以做些什么来加速上下文创建吗

    更新-2017年5月30日

    我使用了EF6源代码并编译了我们自己的版本,以保留一些时间。我们经营着一个相当受欢迎的网站,所以收集大量的时间信息是很棘手的,我没有达到我想要的程度,但基本上我们发现所有的减速都来自于


    该foreach的每次迭代都会命中在我们的上下文中由DBSet定义的每个实体。每个迭代都相对较短。1到。3毫秒,但当您添加我们拥有的254个实体时,它就加起来了。我们还没有弄清楚为什么一开始速度很快,后来又变慢了。

    我将从这里开始解决这个问题,让SAN转向一个更为企业友好的解决方案

    Our context is a fairly large code-first context with around 300 entities 
    
    虽然EF随着时间的推移有了很大的改进,但我仍然会认真考虑在达到100个实体后将其分解(实际上我会在这之前就开始了,但这似乎是许多人所说的一个神奇的数字——同意?)。可以把它看作是为“上下文”而设计的,但是用“域”这个词来代替吗?这样,您就可以向您的执行官推销您正在应用“域驱动设计”来修复应用程序了吗?也许你是为未来的“微服务”而设计的,那么你在一个段落中使用了两个流行语

    在企业领域,我不是EF的超级粉丝,因此我倾向于在高规模或高性能应用程序中避免EF。您的里程可能会有所不同。对于中小企业来说,这可能是完美的。然而,我确实遇到过使用它的客户

    我不确定下面的想法是否完全是最新的,但它们是基于经验的其他一些事情。
    • 预先生成您的观点。它们是查询中最昂贵的部分。这将有助于更大的模型
    • 将模型移动到单独的部件。与其说是性能问题,不如说是我对代码组织的不满
    • 检查您的应用程序、模型,以了解缓存的可能性。查询计划缓存通常可以节省大量时间
    • 使用编译器
    • 可行时,使用NoTracking。如果您不需要该功能,这将是一个巨大的节约
    看起来您已经在应用程序上运行了某种类型的探查器,所以我假设您也查看了SQL查询和可能的性能增益。是的,我知道这不是你想要解决的问题,但从用户的角度来看,这可能会导致整个问题


    针对@WiktorZichia关于没有回答性能问题的评论,在企业系统中消除此类问题的最佳方法是消除实体框架。每个决定都有权衡。EF是一个很好的抽象概念,可以加快开发速度。但它带来了一些不必要的开销,可能会在一定程度上损害系统。现在,从技术上讲,我仍然没有回答“我如何解决这个问题?我正在试图解决这个问题”,所以这可能仍然被视为失败

    DbContext
    实例创建是您唯一的减速吗?您是否正确处理了上下文?例如,使用块?@Alex:web应用程序中每个会话一个上下文?听起来像是一场灾难的邀请。我以前在上一家公司遇到过这种情况。所有操作都是通过存储过程完成的
    Our context is a fairly large code-first context with around 300 entities