如何获取Entity Framework生成的SQL语句

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何获取Entity Framework生成的SQL语句相关的知识,希望对你有一定的参考价值。

参考技术A public string toSql()

using(var db = new SqlEntities())


var Q = db.Sql.Select(cc => cc.id);

return ((ObjectQuery)Q).ToTraceString();



结果:
SELECT [Extent1].[id] AS [id] FROM [dbo].[Sql] AS [Extent1]本回答被提问者和网友采纳

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生成的SQL语句的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Entity Framework 生成和自动递增 Id

如何从 ADO.NET Entity Framework 中的模型生成表?

Entity Framework Core 是如何根据实体类生成模型的?

Entity Framework Core是如何根据实体类生成模型的?

在使用 Entity Framework 数据库优先在表上插入行之前,如何从 PL/SQL 执行触发器?

如何使用 Entity Framework Core 获取主键值