【问题标题】:EF 4.3.1 and EF 5.0 DbSet.Local is slower than an actual Database queryEF 4.3.1 和 EF 5.0 DbSet.Local 比实际的数据库查询慢
【发布时间】:2012-08-26 17:46:32
【问题描述】:

我有一个包含大约 16,500 个城市的表的数据库,以及该数据库的 EF 数据模型(数据库优先)。我使用代码将它们预加载到内存中:

Db.Cities.Load()

...那么当需要使用它们时,我已经尝试了以下每个查询:

Dim cities() As String = Db.Cities.Select(Function(c) c.CityName).ToArray

Dim cities() As String = Db.Cities.Local.Select(Function(c) c.CityName).ToArray

第一个查询很快(约 10 毫秒),但第二个查询第一次运行大约需要 2.3 秒(尽管之后调用它比第一个查询快)。

这没有意义,因为 SQL Server Profiler 会验证第一个查询是否正在访问另一台计算机上的数据库,但第二个不是!

我尝试关闭Db.Configuration.AutoDetectChangesEnabled,并尝试预先生成视图。

我该怎么做才能让.Local 更快? (并非所有运行此应用程序的客户端都将使用快速 LAN。)

【问题讨论】:

  • 我应该注意我使用的是 .NET 4.0 而不是 4.5,因此对“EF 5.0”的测试实际上是在 EF 5.0(又名 EF 4.4.0)的 .NET 4.0 版本上进行的。不幸的是,.NET 4.5 目前不适合这个项目。

标签: .net vb.net entity-framework entity-framework-5 entity-framework-4.3.1


【解决方案1】:

我使用Resharper 的便捷功能了解了Local 属性的来源。您将首先看到对DetectChanges 的调用,如果您正在运行的是上述三行代码,这可能不是您的问题。但随后 EF 为 Local 创建了一个新的 ObservableCollection 并逐项填充它。第一次通话时,其中任何一个都可能代价高昂。

直接针对DbSet 的查询将路由到我确信直接访问内部本地缓存的EF 数据库提供程序。

【讨论】:

  • 赞成为我指出正确的方向;我不知道 ReSharper 允许我深入研究 EF 内部。
  • Resharper 是一个很好的工具。您还将在那里看到 EF 跟踪 Local ObservableCollection。我强烈建议不要访问内部缓存,除非是只读目的。但是我一次又一次地发现,当你进行预加载时缓存在数组或列表中(就像@Akash 说的那样)确实是要走的路。有很多方法可以在不增加太多语法开销的情况下做到这一点。
【解决方案2】:

以下扩展方法将返回一个包含 DbSet 的本地缓存实体的 IEnumerable<T>,而不会产生 DbSet.Local() 方法检测上下文更改和创建 ObservableCollection<T> 对象所产生的启动开销。

<Extension()>
Public Function QuickLocal(Of T As Class)(ByRef DbCollection As DbSet(Of T)) As IEnumerable(Of T)
    Dim baseType = DbCollection.[GetType]().GetGenericArguments(0)
    Dim internalSet = DbCollection.GetType().GetField("_internalSet", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance).GetValue(DbCollection)
    Dim internalContext = internalSet.GetType().GetProperty("InternalContext").GetValue(internalSet, Nothing)
    Return DirectCast(internalContext.GetType.GetMethod("GetLocalEntities").MakeGenericMethod(baseType).Invoke(internalContext, Nothing), IEnumerable(Of T))
End Function

在包含 19,679 个实体的 DbSet 上调用 .QuickLocal 需要 9 毫秒,而在第一次调用时调用 .Local 需要 2121 毫秒。

【讨论】:

  • 啊,如果是这个问题,你可以避免反射,直接访问ObjectStateManager((IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Unchanged | EntityState.Added | EntityState.Modified).Select(entry =&gt; entry.Entity).OfType&lt;T&gt;()
  • 这样比较简单,但是在DbSet上编写扩展方法来用QuickLocal()替换Local()时,没有简单的方法可以得到DbSet对象的DbContext无论如何都要使用反射。
  • 你说得对,你可以用它把Db.Cities.Local改成Db.GetLocalEntities&lt;City&gt;(),但是如果你把Db.Cities传递给一个方法,你就卡住了,你需要反思。
  • 实际上在 EF 源代码entityframework.codeplex.com/SourceControl/latest#src/… 中看到 GetLocalEntities 只是使用 hvd 的代码(实际上有点长,因为 hvd 的 OfType 想法比 EF Where().Select( ))
【解决方案3】:

您为什么不简单地保存第一个查询的字符串列表并使用它。

List<string> cities = db.Cities.Select( x=>x.CityName).ToList();

Local 可能会因为 Select 而变慢,它可能会进行一些一致性检查。

【讨论】:

  • 这就是我的倾向,但我目前正在使用循环在所有 DbSet 上调用 .Load()。应用程序中大约有 77 个表加载到内存中以供快速参考。我怀疑.Local 的第一个调用是创建 ObservableCollection (带有一致性检查),但我们实际上并没有使用 ObservableCollection 功能。如果我没有找到更快的方法来获取本地数据,那么我会将表加载到 List 集合中。
  • 将此作为公认的解决方案,因为依赖内部缓存会引发其他问题,例如使用数据库中的新值更新缓存对象的能力。通过编写 T4 模板来完成繁重的工作,我已经处理了创建、填充和管理大量 List 引用表的开销,因此我只需要根据需要修改 .edmx 文件。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-11
相关资源
最近更新 更多