【问题标题】:How do you minimize the performance hit when upgrading to EF 4.1 from LINQ to SQL?从 LINQ 到 SQL 升级到 EF 4.1 时,如何最大限度地减少性能损失?
【发布时间】:2011-04-04 02:08:42
【问题描述】:

我最近使用 LINQ to SQL 和 SQL Server CE 3.5 to Entity Framework 4.1 Code First 和 SQL Server CE 4.0 更新了一个应用程序,现在它的运行速度明显变慢。我做了一些之前和之后的秒表测试,我的应用程序的大多数主要操作似乎平均慢了大约 40%。

我使用 EF Code First 的所有默认策略和配置,除了禁用级联删除。

当我最初发布这个问题时,我专注于一个似乎需要特别长时间的查询,但后来我意识到它只是在第一次运行时特别慢(请参阅下面的评论线程)。

我现在认为我看到的是大多数查询运行速度较慢 - 并没有明显变慢,但速度慢到足以快速加起来,因为应用程序执行的大多数操作都涉及多个查询。

这个应用程序的数据库非常小。 SQL CE (.sdf) 文件只有 458 KB,最大的表不到 250 条记录。

这是一个示例 POCO 类:

public class Target
{
    public int Id { get; set; }
    public int TrialDefinitionId { get; set; }
    public int Number { get; set; }
    public int X { get; set; }
    public int Y { get; set; }
    public string Phase { get; set; }
    public virtual TrialDefinition TrialDefinition { get; set; }
}

我所有的类都遵循这个基本模式(简单类型 + 虚拟属性来获取通过外键链接的对象)。我有一个类使用ICollection 来获取多对一关系的列表。

最后说明:我使用存储库模式作为中介,存储库的每次使用都放在using 块中。对于“获取”操作,一旦我从数据库中获取了我需要的数据,这会导致实体分离。

有没有人有任何具体的策略来提高我的 EF Code First 应用程序的性能?请记住,我还没有机会详细阅读 EF。我主要只是想尽可能快速、轻松地从 LINQ 迁移到 SQL 到 EF。对我来说最有用的答案是更改特定策略或配置或其他设置。

【问题讨论】:

  • 是什么让你觉得 0.25 秒不合理?您是否还有其他性能数据可以让这款产品脱颖而出?看起来时间完全花在将数据从 DB 传输到 .NET 列表对象上。
  • 是仅在第一次访问时花费的时间,还是您连续多次运行此代码?我试图确定是否大部分时间都用于建立初始连接并让数据库引擎运行。完成后,它会开始更快地执行吗?
  • 每次都需要这么长时间吗?我发现让第一个查询“假脱机”会产生很大的开销。我不知道在第一次调用时初始化了哪些内部结构,但它们似乎很重要。之后,它通常是一帆风顺的。
  • 0.25 秒只是不合理的,如果对整个应用程序的性能有要求的话。如果是这种情况,您需要在对 DB 狂吠之前测量整个应用的性能。
  • +1 不明白为什么这应该被否决

标签: c# linq-to-sql entity-framework sql-server-ce code-first


【解决方案1】:

最后说明:我使用存储库模式作为中介,存储库的每次使用都放置在 using 块中。对于“获取”操作,一旦我从数据库中获取了我需要的数据,这会导致实体分离。

这不是必需的......

  1. Entity Framework 的默认架构已经实现了存储库模式。
  2. 保持 ObjectContext 处于活动状态并不意味着您保持与数据库的连接处于活动状态。
  3. 只有当您从数据库加载或保存更改时,才会从连接池中抓取一个新连接并执行操作。

当然,using块会变慢,因为每个using块都会做following,

  1. 初始化上下文(需要从资源中加载元数据)
  2. 验证几件事
  3. 打开与 DB 的连接
  4. 执行您的任务
  5. 清理并关闭数据库

现在前两个步骤肯定会花费大量时间,并且您的应用中将有多个相同类型的对象寿命更长,因为每个新上下文都会为每个查询创建相同对象的新副本。

Entity Framework 已经实现了 Identity Map,这意味着它会在上下文的整个生命周期内保持对象处于活动状态,并且同一主键只有一个对象副本,这不仅可以节省内存,而且执行速度更快。

我建议不要对每个查询或较小的步骤使用 Using 块,而是应该在应用程序的整个生命周期内保持 ObjectContext 处于活动状态。而且您根本不需要实现缓存或存储库。

【讨论】:

  • 谢谢你,阿卡什。这需要一些重构,但我绝对想尝试一下。听起来它可能会解决我的速度问题并且使我的代码更简单、更易于维护。
  • 一个问题,不过...为什么DbContext 执行IDisposable 如果您不应该在每次使用后处理它?
  • 好吧,是否应该处置它完全取决于情况。例如,在无状态 Web 服务架构中,您应该在获得结果后进行处置。在您的情况下,您将在机器上每个用户只有一个实例,因此保持对象缓存并让对象上下文寿命更长也不错。但是在带有 web 服务的服务器上,如果每个人都将上下文保持更长时间,就会导致内存不足。
  • 假设您的数据库在应用程序运行时断开连接。如果您在应用程序生命周期内只有一个对象上下文,您将如何从中恢复?对它们进行批处理很好,但在应用程序的整个生命周期中保留一个很少是正确的解决方案。
  • @Akash,“每次上下文初始化时加载元数据”不正确。它实际上缓存。 “Entity Framework 自动缓存元数据和它需要的其他信息在应用程序域中,ADO.NET 池化数据库连接,因此每次重新创建上下文是一个快速操作。”
【解决方案2】:
【解决方案3】:

阅读herehere 了解实体框架的内部工作。它与 EFv4 和 ObjectContext API 有关,但带有 DbContext API 的 EFv4.1 只是 EFv4 的包装。

如果您觉得查询速度很慢,请尝试在同一上下文中执行两次,并在上下文的不同实例上执行两次。第一个测试将检查问题是否存在于对象实现中,因为对象将仅在第一个查询中实现,第二个测试将检查上下文初始化是否有任何问题(如果您使用标准上下文创建和连接,则不应发生这种情况字符串)。

将执行与编译查询进行比较也会很有趣,但我感觉编译查询不是 DbContext API 的一部分。

【讨论】:

    【解决方案4】:

    当您更改为 Code First 方法时,这是否改变了数据库的结构?

    我的猜测是肯定的,这就是导致性能变化的原因。

    我还注意到你班上的一件事,你有:

    public int TrialDefinitionId { get; set; }
    

    和:

    public virtual TrialDefinition TrialDefinition { get; set; }
    

    这两个都需要吗?

    【讨论】:

    • 我的数据库结构实际上保持不变。至于为什么我同时拥有TrialDefinitionIdTrialDefinition,我相信这是普遍做法。它允许您访问您选择的记录的所有字段以及通过外键链接的任何记录的所有字段。这正是它在 LINQ to SQL 中的工作方式,我认为它不会导致性能下降,因为除非使用数据,否则实际上不会从数据库中选择数据。见weblogs.asp.net/scottgu/archive/2010/07/16/…
    • 你也有和以前一样的索引吗?
    • 我从未在原始数据库中手动创建任何索引,所以我怀疑 Code First 生成的数据库会有不同的索引,但我真的不确定。
    猜你喜欢
    • 2014-08-27
    • 2020-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多