entity framework 性能真那么差

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了entity framework 性能真那么差相关的知识,希望对你有一定的参考价值。

说EF性能差的,目测是根本不会使用EF的,或者说是把EF用的的很烂的人。
EF目前是6.1.3。针对code first以及SQL脚本优化做了非常多的工作。举个简单的例子。现在估计还有大部分.NET程序员在使用数据脚本分页的时候还在用着古老的top分页吧?好一点的知道row_number。但是EF会根据你连接SQL版本(仅讨论SQLserver)进行不同版本的分页支持。例如sql2012以上的版本。EF会自动采用FETCH NEXT进行分页,fn和top的性能对比,这个就不说了。
再举个例子。之前给公司某同事擦屁股,有一个多表多关联复杂查询并且内部要实现一些函数操作。采用linq to ef编写的。测试反馈说查询速度巨慢。我打开sql profile看了一下。此君接近90行的linq,生成了2000行的SQL。于是我逐步重写。最后变为40行的Linq,生成60行的脚本,打开速度立马飞跃到毫秒级。
以上是说的读取性能方面的问题。下面说一下写的问题,目前EF对批量写批量UP操作的确支持不是很好(不要提addrange) 但是可以通过第三方EF插件实现对数据库的bulk操作。大量写的效率基本和在sql里进行bulk几乎差不多。
所以不是EF性能不好,而是你不会用罢了
参考技术A 模式决定一切,他底层的模式决定了,性能好不到哪里去。用得差的固然慢,用得好的性能确实有不错的提升,但是无论怎么样,这种映射的模式决定了他确定有相当的性能损耗,比如: 相对于存储过程来说,1、sql上行传输量大 2、输出一些无关的字段,这对于大表尤其明显,3对于一些复杂的逻辑,需要拿到数据到.net代码中处理,来来回回的交互远没有在存储过程中本地内存中处理快。4、对于一些需要使用事务,使用锁的地方,事务,锁占用的时间效率==都是问题,所以EF只适合一些小的应用,对于大的比较复杂,性能高的系统还是不适用,不是说EF不好,关键看应用场景,根据不同的场景选择最合适的技术架构才是最正确的。 参考技术B 自问自答玩的很嗨吧!
这东西烂是因为不可测,需要额外花费精力去关注运作的细节,要优化性能直接优化sql查询不直观么?
参考技术C ef大而全,然而我发现ef没用武之地,ef性能现在提升很多,但是这是说预热后执行sql的性能,小项目哪里有多余的资源给你几十秒先预热,基本都是冷启动;大项目对性能要求又特别高,而且对于大项目来说EF真心不好动数据库,万一有需求变化能改死人

Entity Framework Core 计数没有最佳性能

【中文标题】Entity Framework Core 计数没有最佳性能【英文标题】:Entity Framework Core count does not have optimal performance 【发布时间】:2016-11-11 22:08:46 【问题描述】:

我需要通过某个过滤器获取记录的数量。

理论上这条指令:

_dbContext.People.Count (w => w.Type == 1);

它应该生成如下 SQL:

Select count (*)
from People
Where Type = 1

但是,生成的 SQL 是:

Select Id, Name, Type, DateCreated, DateLastUpdate, Address
from People
Where Type = 1

正在生成的查询需要更长的时间才能在具有许多记录的数据库中运行。

我需要生成第一个查询。

如果我这样做:

_dbContext.People.Count ();

实体框架生成以下查询:

Select count (*)
from People

.. 运行速度非常快。

如何生成第二个查询,将搜索条件传递给计数?

【问题讨论】:

你的第一次尝试“应该”得到你想要的。它似乎正在执行 .Count([predicate]) 的 IEnumerable 版本,而不是会生成预期 SQL 的 IQueriable 版本……某些原因导致您的代码选择了不正确的扩展名。 _dbContext.People 声明为什么类型? @Theo 我不使用 IEnumerable 版本。我已经验证过了! 尝试升级到 EF Core 1.1.0。 EF 团队对查询生成进行了一些改进——我在我的项目中发现了更优化的 JOIN 查询,我的麻烦也将解决。 EF Core 很年轻。 @Renatto Machado 我刚刚创建了一个空项目和一个示例数据库,其中包含一个像您这样的表,安装了 EF Core 1.1,返回的查询是正确的。也许你真的应该更新库。 【参考方案1】:

这里没有太多要回答的。如果您的 ORM 工具无法从简单的 LINQ 查询中生成预期的 SQL 查询,那么您无法通过重写查询来让它这样做(而且您一开始就不应该这样做)。

EF Core 有一个在 LINQ 查询中混合客户端/数据库评估的概念,这允许他们发布具有不完整/非常低效的查询处理的 EF Core 版本,就像你的情况一样。

摘自Features not in EF Core(注意not这个词)和Roadmap:

改进了翻译以使更多查询能够成功执行,并在数据库中评估更多逻辑(而不是在内存中)。

简而言之,他们正计划改进查询处理,但我们不知道何时会发生这种情况以及程度如何(请记住,混合模式允许他们认为查询“有效”)。

那么有哪些选择呢?

首先,远离 EF Core,直到它真正有用为止。回到 EF6,它没有这样的问题。 如果您不能使用 EF6,请使用最新的 EF Core 版本进行更新。

例如,在 v1.0.1 和 v1.1.0 中,您的查询都会生成预期的 SQL(已测试),因此您只需升级即可,具体问题将不复存在。

但请注意,随着改进,新版本会引入错误/回归(例如,您可以在此处看到 EFCore returning too many columns for a simple LEFT OUTER join),因此请自行承担风险(并再次考虑第一个选项,即 Which One Is Right for You :)

【讨论】:

了解你的 ORM 可以和不能在这里重写是关键 - 永远不要做任何假设 - 特别是在 EFCore 的早期阶段【参考方案2】:

尝试使用此 lambda 表达式更快地执行查询。

_dbContext.People.select(x=> x.id).Count();

【讨论】:

这对我不起作用。我需要在“Where”方法上传递谓词,当我这样做时,问题就出现了。 _dbContext.People.where(x=x.filterColumname == 输入参数).select(x=> x.id).Count();【参考方案3】:

试试这个

(from x in _dbContext.People where x.Type == 1 select x).Count();

或者你可以做它的异步版本:

await (from x in _dbContext.People where x.Type == 1 select x).CountAsync();

如果这些都不适合您,那么您至少可以通过以下方式提高查询效率:

(from x in _dbContext.People where x.Type == 1 select x.Id).Count();

await (from x in _dbContext.People where x.Type == 1 select x.Id).CountAsync();

【讨论】:

【参考方案4】:

如果您想优化性能并且当前的 EF 提供程序(还)不能产生所需的查询,您可以始终依赖 raw SQL。

显然,这是一种权衡,因为您使用 EF 来避免直接编写 SQL,但如果您要执行的查询无法使用 LINQ 表达,或者使用 LINQ 查询,则使用原始 SQL 会很有用导致发送到数据库的 SQL 效率低下。

原始 SQL 查询示例如下所示:

var results = _context.People.FromSql("SELECT Id, Name, Type, " +
                                      "FROM People " +
                                      "WHERE Type = @p0",                                                     
                                      1);

据我所知,传递给 FromSql 扩展方法的原始 SQL 查询目前要求您返回模型类型,即可能尚不支持返回标量结果。

但是,您始终可以返回到普通的 ADO.NET 查询:

using (var connection = _context.Database.GetDbConnection())

    connection.Open();

    using (var command = connection.CreateCommand())
    
        command.CommandText = "SELECT COUNT(*) FROM People WHERE Type = 1";
        var result = command.ExecuteScalar().ToString();
    

【讨论】:

如果上下文不是从头开始创建的,连接上的关闭方法应该被调用而不是通过“使用”处理。因为除非重新创建 Context,否则无法重用此 DBConnection。所以这有点可悲,但在调用 Open() 之前,您应该在每次使用它时检查它是否尚未打开以及 Close() 它。 :(github.com/aspnet/EntityFrameworkCore/issues/7810【参考方案5】:

Entity Framework Core 的早期版本之一似乎存在一些问题。不幸的是,您没有指定确切的版本,所以我无法深入研究 EF 源代码来判断到底出了什么问题。

为了测试这个场景,我安装了最新的 EF Core 包并设法得到了正确的结果。

这是我的测试程序:

下面是 SQL Server Profiler 捕获的生成的 SQL:

如您所见,它符合所有预期。

这是 packages.config 文件的摘录:

...
<package id="Microsoft.EntityFrameworkCore" version="1.1.0" targetFramework="net452" />
...

因此,在您的情况下,唯一的解决方案是更新到在撰写本文时为 1.1.0 的最新包。

【讨论】:

你用什么来查看生成的 SQL 查询? 在这种情况下,我使用 SQL Server Profiler 直接监控数据库服务器。它允许查看所有已执行的语句,甚至那些不是来自 EF 的语句。 docs.microsoft.com/en-us/sql/tools/sql-server-profiler/… 不幸的是,它现在已被弃用,您应该改用名为 Extended Events 的东西。【参考方案6】:

这是否得到你想要的:

_dbContext.People.Where(w => w.Type == 1).Count();

【讨论】:

【参考方案7】:

我在这里使用的是 EFCore 1.1。

如果 EFCore 无法将整个 Where 子句转换为 SQL,则可能会发生这种情况。这可以是像DateTime.Now 这样简单的东西,甚至可能都不会考虑。

下面的语句会产生一个 SQL 查询,一旦它加载了整个表,它将令人惊讶地运行 SELECT * 和 C# .Count()

   int sentCount = ctx.ScheduledEmail.Where(x => x.template == template &&
                   x.SendConfirmedDate > DateTime.Now.AddDays(-7)).Count();

但此查询将运行 SQL SELECT COUNT(*),正如您所期望/希望的那样:

   DateTime earliestDate = DateTime.Now.AddDays(-7);
   int sentCount = ctx.ScheduledEmail.Where(x => x.template == template 
                   && x.SendConfirmedDate > earliestDate).Count();

疯狂但真实。幸运的是,这也有效:

   DateTime now = DateTime.Now;
   int sentCount = ctx.ScheduledEmail.Where(x => x.template == template &&
                   x.SendConfirmedDate > now.AddDays(-7)).Count();

【讨论】:

现在大部分已在 EFCore 2.1 中修复 - 但请注意日志/控制台消息,因为某些复杂情况可能无法正常工作。【参考方案8】:

对不起,但是……

使用 where 子句的查询很慢的原因可能是因为您没有为数据库提供快速执行它的方法。

如果从 People 查询中选择 count(*),我们不需要知道每个字段的实际数据,我们可以只使用一个小索引,其中没有所有这些字段,因此我们不必花费我们的慢I/O 开启。数据库软件足够聪明,可以看到主键索引需要最少的 I/O 来进行计数。 pk id 需要的空间比整行要少,因此您可以获得更多的每个 I/O 块的计数,因此您可以更快地完成。

现在,对于带有 Type 的查询,它需要读取 Type 以确定它的值。如果您希望查询更快,则应该在 Type 上创建索引,否则它将不得不进行非常缓慢的全表扫描,读取所有行。当您的价值观更具歧视性时,它会有所帮助。一列性别(通常)只有两个值并且不是很区分,其中每个值都是唯一的主键列是高度区分的。更高的区分值将导致更短的索引范围扫描和更快的计数结果。

【讨论】:

【参考方案9】:

我过去使用搜索查询来计算行数是

_dbContext.People.Where(w => w.Type == 1).Count();

这也可以通过

来实现
List<People> people = new List<People>();
people = _dbContext.People.Where(w => w.Type == 1);
int count = people.Count();

如果您进一步需要,这样您也可以获得人员列表。

【讨论】:

但问题不是问如何在 Linq 上计数,而是关于 EF Core 在计数时生成的查询。 感谢@Gabriel 的解释。对于其他对此投反对票的人请务必提及原因,以便我们改进:) 无论如何谢谢 您不想将所有人员加载到内存中,只是为了计算表中有多少人。如果我们有超过 100 万条记录会怎样?你的服务器会死

以上是关于entity framework 性能真那么差的主要内容,如果未能解决你的问题,请参考以下文章

想请问一下Adp.net与Ado.net Entity Framework、NHibernate性能对比

使用Entity Framework时要注意的一些性能问题

Entity Framework Core 性能优化

Entity Framework Core 性能优化

Entity Framework Core 计数没有最佳性能

Entity Framework Core 性能优化