实体框架太慢了,我有什么选项?

94

我一直遵循“不要过早优化”的原则,使用Entity Framework编写了我的WCF服务。

然而,我进行了性能分析,发现Entity Framework太慢了。我的应用程序每1.2秒处理2个消息,而我正在重写的(传统)应用程序在同样的时间内可以处理5-6个消息。(传统应用程序调用存储过程来访问其数据库。)

我的性能分析指出,每条消息Entity Framework占用大部分时间。

那么,我的选择是什么?

  • 是否有更好的ORM可用?
    (支持常规对象读写且速度较快的东西。)

  • 是否有方法使Entity Framework更快?
    注意:当我说更快时,我的意思是在长期内,而不是第一次调用。第一次调用较慢(15秒/消息),但这不是问题。我只需要在接下来的消息中快速响应。)

  • 某些神秘的第三种选择,将帮助我从我的服务中获得更多速度。

注意:我的大多数数据库交互都是创建和更新。我非常少地进行选择和删除。


7
有些答案指向查询。根据我的经验,EF 中的缓慢与查询关系不大,而是与实例的创建以及更改跟踪所带来的物化成本有关。不幸的是,我没有解决问题的灵丹妙药,这只是一条评论,但我建议您检查分析结果是否显示高的物化成本,如果是,研究如何降低这些成本。 - Anthony Pegram
1
@Vaccano,不,实体化是从数据库中获取数据并实例化和填充对象图以表示该数据的过程。我说的不是首次运行性能,因为代码被JIT编译(甚至当Sql Server创建查询执行计划时),而是每次以对象形式获取数据时会发生什么。 - Anthony Pegram
@AndrewRimmer - 对于第一次运行,它会更快...对于正常的“应用程序中间处理”并没有做太多工作。 - Vaccano
Entity Framework 慢?我不这么认为。Stackoverflow 是工作证明。如果 EF 存在异常延迟的问题,那就是开发人员的问题。就这么简单。 - Noah R
1
EF 的真正优势在于其灵活性,可以适用于任何数据源。然而,如果你总是使用 MS-SQL,那么不使用存储过程来配合 EF 就没有太大意义了(也许干脆就不要使用 EF——因为它肯定会在开发过程中增加更多“它现在在做什么?”的问题)。如果你讨厌混淆或需要精细控制,那么 EF 并不适合你。在非常复杂的项目中,EF 确实需要更长时间的调试(并且更容易破坏数据库!)。 - MC9000
显示剩余8条评论
13个回答

87
事实上,像Entity Framework这样的产品总是会很慢且效率低下,因为它们要执行更多的代码。
我也觉得有些荒唐,人们建议优化LINQ查询、查看生成的SQL、使用调试器、预编译、采取许多额外的步骤等,即浪费很多时间。没有人说——简化!每个人都想通过采取更多步骤(浪费时间)来进一步复杂化事情。
一个常识性的方法是根本不使用EF或LINQ,而是使用纯SQL。这没有任何问题。仅仅因为程序员之间存在羊群心理,他们感觉需要使用每一个新的产品,这并不意味着这是好的或有效的。大多数程序员认为,如果他们将每一家大公司发布的新代码都整合在一起,这会让他们成为更聪明的程序员;这是完全不正确的。聪明的编程主要是关于如何以最少的头痛、不确定性和最短的时间做更多的事情。记住——时间!这是最重要的因素,所以尝试找到不浪费时间解决用于符合某些奇怪的所谓“模式”的糟糕/臃肿代码问题的方法。
放松,享受生活,休息一下,不要使用额外的功能、代码、产品、“模式”。人生苦短,代码的寿命更短,它当然不是火箭科学。删除像LINQ、EF等层,你的代码将运行高效,可扩展,而且仍然易于维护。太多的抽象是一种糟糕的“模式”。
这就是你问题的解决方案。

175
这就是弃婴抛洗水的做法。你应该优化瓶颈,因为EF在大多数情况下都很快,只在少数地方速度较慢并不值得完全抛弃。为什么不两者兼顾呢?EF可以处理存储过程和原始SQL查询。我刚刚将一个需要10秒以上的LINQ-to-SQL查询转换为一个只需要约1秒的存储过程,但我不会放弃所有的LINQ-to-SQL。它在其他简单的情况下节省了大量时间,并且代码更少、出错的可能性更小,查询已由编译器验证并与数据库匹配。代码越少,维护越容易,错误的空间也越小。 - JulianR
12
总的来说,你的建议是不错的,但我认为因为它们在 10% 的情况下表现不佳而放弃 EF 或其他抽象化是不正确的。 - JulianR
58
仅仅因为使用普通的SQL语句,就能够轻松地维护代码吗?这并不适用于那些有大量业务逻辑的庞大应用程序。编写复杂且可重复使用的SQL语句并不是一件容易的事情。就我个人而言,我曾经在使用EF时遇到一些性能问题,但是与正确使用ORM所带来的快速应用开发和DRY(如果涉及到较高复杂程度)相比,这些问题根本不值一提。 - MemeDeveloper
14
太多的抽象化是一个不好的“模式”。 - Makach
66
“EF将始终缓慢而低效。” 我不明白为什么您要断言这样的话是绝对正确的。加入更多的层会使某些事情变慢,但是这种差异是否显着完全取决于情况,例如正在执行的数据量和查询类型。对我来说,这就像说“C#将始终缓慢而低效”,因为它比C++更高层级。然而,许多人选择使用它,因为生产力的提高远远超过了性能损失(如果有的话)。相同的情况也适用于EF。 - Despertar
显示剩余11条评论

50
你可以从对Entity Framework实际发出的SQL命令进行性能分析开始。根据你的配置(POCO、Self-Tracking entities),还有很多优化的空间。你可以使用ObjectSet<T>.ToTraceString()方法调试SQL命令(在Debug和Release模式下不应该有区别)。如果你遇到需要进一步优化的查询,可以使用一些投影来向EF提供更多关于你想要实现的内容信息。
示例:
Product product = db.Products.SingleOrDefault(p => p.Id == 10);
// executes SELECT * FROM Products WHERE Id = 10

ProductDto dto = new ProductDto();
foreach (Category category in product.Categories)
// executes SELECT * FROM Categories WHERE ProductId = 10
{
    dto.Categories.Add(new CategoryDto { Name = category.Name });
}

可以替换为:

var query = from p in db.Products
            where p.Id == 10
            select new
            {
                p.Name,
                Categories = from c in p.Categories select c.Name
            };
ProductDto dto = new ProductDto();
foreach (var categoryName in query.Single().Categories)
// Executes SELECT p.Id, c.Name FROM Products as p, Categories as c WHERE p.Id = 10 AND p.Id = c.ProductId
{
    dto.Categories.Add(new CategoryDto { Name = categoryName });
}
我仅仅是凭借自己的想法写了这段代码,所以这并不是完全可执行的。但是如果你告诉EF有关查询的所有信息(在这种情况下,我们需要类别名称),EF实际上会进行一些不错的优化。但这不像急切加载(db.Products.Include("Categories")),因为投影可以进一步减少要加载的数据量。

43
这个回应听起来很合理,但当你意识到匿名类型在定义它们的方法之外是不可访问的时,就会感到困惑。如果你想加载一个复杂对象而又不想写一个臃肿的代码,那么你需要将新的匿名类型反序列化成某种POCO。这似乎也很合理,但当你意识到这样做实际上等同于重新编写自己的Entity Framework时,这就有些扯了。 - Doug
6
这使我的速度提高了15到20倍。 - Dave Cousineau
13
有趣且有帮助的回复,很长时间后仍然有效。@Doug:这并不是胡说八道,因为您只需要在确实需要使用额外好处的少数查询中进行优化(使用投影)。EF和POCO为您提供了合理的默认设置,这非常棒! - Victor
2
大多数应用程序都有专门用于仅查看的视图模型,对吧?最好在提取数据时尽可能多地进行映射。 - Casey
5
我曾经认为ORM是未来的趋势,因为它们似乎很合理。但当我开始使用它们时,我发现Dapper更好用。现在,当看到像这样的解决方案时,我会因其快速增加的复杂性而感到不安。用C#编写抽象的SQL语句并不是生活中的正确方式。 - Michael Silver
显示剩余3条评论

38
一种建议是仅将LINQ用于单记录CRUD语句。对于更复杂的查询、搜索、报告等,编写存储过程并按MSDN上所述将其添加到Entity Framework模型中。
这是我在我的几个站点中采用的方法,似乎是生产力和性能之间的良好折衷。Entity Framework并不总是为手头的任务生成最有效的SQL。而且,与其花时间弄清原因,为更复杂的查询编写存储过程实际上为我节省了时间。一旦熟悉了这个过程,将存储过程添加到EF模型中并不会太麻烦。当然,将其添加到模型中的好处是您可以获得使用ORM带来的所有强类型好处。

你了解脚手架中使用的方法,例如db.athlete.find(id)等吗?它们与ADO.NET或dapper相比如何表现? - It's a trap

15
如果你仅仅是获取数据,使用MergeOption.NoTracking能够帮助提升性能。这样做可以告诉EF不要跟踪所获取的实体。EF只会生成查询语句、执行它并将结果反序列化为对象,但不会尝试跟踪实体更改或其他类似的操作。对于查询比较简单(不需要等待数据库返回很长时间)的情况,我发现将其设置为NoTracking可以使查询性能提高一倍。
请参阅MSDN文章中有关MergeOption枚举的内容: Identity Resolution, State Management, and Change Tracking 这篇文章似乎涉及了EF性能: Performance and the Entity Framework

9
在执行此操作之前,建议先阅读这里的内容:https://dev59.com/fWox5IYBdhLWcg3wWjHQ。 - YodasMyDad

6

你说你已经对应用程序进行了性能分析。那么,ORM也进行了性能分析吗?Ayende有一个EF性能分析工具,可以帮助你找到需要优化的EF代码部分。你可以在这里找到它:

http://efprof.com/

请记住,如果需要提高性能,您可以在ORM旁边使用传统的SQL方法。

是否有更快/更好的ORM? 根据您的对象/数据模型,您可以考虑使用其中一个微型ORM,例如DapperMassivePetaPoco

Dapper网站发布了一些比较基准,这将为您提供它们与其他ORM的比较。但需要注意的是,微型ORM不支持EF和NH等完整ORM的丰富功能集。

你可能想要看一下RavenDB。这是一个非关系型数据库(来自Ayende),它允许您直接存储POCO,无需映射。RavenDB针对读取进行了优化,并通过消除操纵架构和将对象映射到该架构的需要,使开发人员的生活变得更加轻松。但是,请注意,这是一种与使用ORM方法明显不同的方法,并且这些方法在产品网站中有详细说明。

4

我发现 @Slauma 在这里提供的答案对于提高速度非常有用。我在插入和更新时使用了相同的模式-性能飙升。


3
从我的经验来看,问题不在于 EF,而是 ORM 方法本身。
一般来说,所有的 ORM 都会遇到 N+1 问题、未优化查询等问题。我最好的猜测是追踪导致性能下降的查询,并尝试调整 ORM 工具,或使用 SPROC 重写那些部分。

3
人们一直告诉我这个。但是,我将使用老式的ADO设置一个简单的选择语句,然后使用EF上下文和EF进行相同的简单选择,而且EF始终要慢得多。我真的很想喜欢EF,但它总是让生活变得更加困难而不是更轻松。 - Sinaesthetic
1
当然会慢一些。同样的道理,使用Linq to Objects编写的代码通常比没有使用它的代码慢。问题不是真正它是否更快或者和手写的查询一样快(当底层仍需发出手动查询时,它怎么可能更快呢?),而是1)它是否仍然足够快以满足您的需求2)它是否节省了您编写代码的时间3)其好处是否抵消了成本。基于这些因素,我认为EF适用于许多项目。 - Casey
@Sinaesthetic 我还要补充一点,如果你不使用ORM,往往会出现的情况是并不是每个SQL查询都被优化和调整,而是应用程序最终会开发出一种内部的、有机的、支持不足、性能不佳的ORM,除非你的团队非常纪律且非常关注性能。 - Casey

1

1
我也遇到了这个问题。虽然EF表现良好,但它的速度确实很慢。在大多数情况下,我只是想查找记录或更新/插入记录。即使是这样简单的操作也很慢。我从一个表中获取了1100条记录放入List中,使用EF花费了6秒钟。对我来说,这太长了,甚至保存也太慢了。
最终,我自己开发了一个ORM。我从数据库中获取了相同的1100条记录,我的ORM只花费了2秒钟,比EF快得多。我使用ORM时几乎一切都是瞬间完成的。现在唯一的限制是它仅适用于MS SQL Server,但可以更改为与其他数据库如Oracle一起使用。我目前在所有项目中都使用MS SQL Server。
如果您想尝试我的ORM,请访问以下链接和网站:

https://github.com/jdemeuse1204/OR-M-Data-Entities

或者如果您想使用NuGet:

PM> Install-Package OR-M_DataEntities

文档也在其中


0
我使用过 EF、LINQ to SQL 和 Dapper。Dapper 是最快的。 例如:我需要 1000 条主记录,每条记录有 4 条子记录。我使用 LINQ to SQL,需要大约 6 秒钟的时间。然后我切换到了 Dapper,从单个存储过程中检索了 2 个记录集,并为每个记录添加了子记录。总时间为 1 秒钟。
此外,存储过程使用带有 CROSS APPLY 的表值函数,我发现标量值函数非常慢。
我的建议是使用 EF 或 LINQ to SQL,在某些情况下切换到 Dapper。

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