【问题标题】:entity framwork core generates weird sql实体框架核心生成奇怪的sql
【发布时间】:2019-03-16 00:49:52
【问题描述】:

我有一个正在尝试优化的现有 LINQ 查询。我有以下实体类型(简体)

public class Account
{
    public int Id { get; set; }
    public IEnumerable<OpportunityInfo> Opportunities { get; set; }
}

public class Opportunity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool Active { get; set; }
    public IEnumerable<Quote> Quotes { get; set; }
}

public class Quote
{
}

这是一个标准的账户到报价机会层次结构。没什么特别的。我在 ASP.NET Core 控制器索引方法上使用了以下查询。我从报价开始并向后工作,因为查询和机会报价之间存在动态查询逻辑,必须基于报价。否则我会从顶部开始。

var query = from o in Quotes select o;

额外的查询逻辑(过滤和排序)

var opportunityQuotes = from o in query
    group o by new
    {
        accountId = o.Opportunity.AccountId,
        accountName = o.Opportunity.Account.Name,
        active = o.Opportunity.Account.Active,
    }
    into p
    select new 
    {
        Id = p.Key.accountId,
        Name = p.Key.accountName,
        Active = p.Key.active,
        Opportunities =
            (from q in p
                group q by new
                {
                    Id = q.Opportunity.Id,
                    Name = q.Opportunity.Name,
                    Active = q.Opportunity.Active
                }
                into r
                 select new 
                 {
                    Name = r.Key.Name,
                    Id = r.Key.Id,
                    Active = r.Key.Active,
                    Quotes = r
                 })
    };

opportunityQuotes.Dump();

此查询生成以下 SQL。

SELECT [o].[Id], [o].[ARRValue], [o].[AccountId], [o].[AdjustedArr], ...
FROM [Quotes] AS [o]
LEFT JOIN [Opportunities] AS [o.Opportunity] ON [o].[OpportunityId] = [o.Opportunity].[Id]
INNER JOIN [Accounts] AS [o.Account] ON [o].[AccountId] = [o.Account].[Id]
ORDER BY [o].[AccountId], [o.Account].[Name], [o.Account].[Active]
GO


SELECT [q.Opportunity0].[Id], [q.Opportunity0].[Name], [q.Opportunity0].[Active]
FROM [Opportunities] AS [q.Opportunity0]
GO

SELECT [q.Opportunity0].[Id], [q.Opportunity0].[Name], [q.Opportunity0].[Active]
FROM [Opportunities] AS [q.Opportunity0]
GO

SELECT [q.Opportunity0].[Id], [q.Opportunity0].[Name], [q.Opportunity0].[Active]
FROM [Opportunities] AS [q.Opportunity0]
GO

实际上,它会针对每个机会生成查询,但为了简洁起见,我将其省略了。我认为 EF 不应为每个报价生成单独的查询。其实如果我把查询中的 .Name 和 .Active 键参数注释掉如下图:

group q by new
{
    Id = q.Opportunity.Id,
    // Name = q.Opportunity.Name,
    // Active = q.Opportunity.Active
}

并在 select 子句中注释掉相应的变量,它会生成更清晰的 sql。

SELECT [o].[Id], [o].[ARRValue], [o].[AccountId], ...
FROM [Quotes] AS [o]
LEFT JOIN [Opportunities] AS [o.Opportunity] ON [o].[OpportunityId] = [o.Opportunity].[Id]
INNER JOIN [Accounts] AS [o.Account] ON [o].[AccountId] = [o.Account].[Id]
ORDER BY [o].[AccountId], [o.Account].[Name], [o.Account].[Active]
GO

我感到困惑的原因是 .Name 和 .Active 在完全相同的对象中,它们以与 .Id 字段相同的方式分组在键中,因此我不明白为什么 EF 会改变它行为只需添加额外的组值。有人可以解释这种行为吗?

【问题讨论】:

  • 为什么需要.Include() 电话?据我所知,您正在从所有对象中加载所需的一切。也许,这就是导致问题的原因。另外,您的问题中的第二个Opportunity 课程在做什么?其中一个有不同的名称,还是我们可以忽略一个?
  • 我可能不需要包含。让我看看这是否有帮助。当我写下这个问题时,第二个 Opportunity 课程只是一个错误。感谢您指出。
  • 我返回并删除了 .Include() ,它对生成的 SQL 没有任何影响。感谢您的建议。
  • 你真的直接从Quote引用Account实体,还是从Account->Opportunity->Quote的层次结构?如果您有直接从报价到帐户的引用,也从通过机会到帐户的报价间接引用,它可能会分散 EF 的注意力。我目前正在尝试在本地重现该行为。
  • 嗯,刚刚注意到我们的名字。 “herold”正在回答“国王”:-)

标签: c# asp.net-core entity-framework-core


【解决方案1】:

让我们退后一步,从不同的角度来看待它:如果您要手动编写 SQL 查询,并希望在一个查询中获取所需的所有数据,那么您将获得大量重复数据以获取机会和帐户。您也可以在此处执行此操作:

var query = from o in Quotes select o;

var oppQuotes = from o in query
select new 
{
    AccountId = o.Opportunity.Account.Id,
    AccountName = o.Opportunity.Account.Name,
    // ... and so on, with all the fields you expect to use.
    OpportunityId = o.Opportunity.Id,
    OpportunityName = o.Opportunity.Name,
    // ... and so on, with all the fields you expect to use.
    QuoteId = o.Id,
    QuoteName = o.Name,
    // ... and again, you get the point.
};

然后,只需在其上执行.AsEnumerable(),并在您的 C# 代码中执行分组。无论如何,数据库将无法优化任何内容。

var opportunityQuotes = from q in oppQuotes.AsEnumerable()
group q by new { q.AccountId, q.AccountName }
into accounts
// ... and so on.

对于您的问题,为什么 EF 正在创建奇怪的查询,我不知所措。

无论如何,考虑如何创建 sql 代码以最有效地获取所需数据而不依赖 EF 来“做正确的事”总是好的。在许多情况下,它会,在其他情况下,它会完全炸毁你的脸。当您需要查询时,请考虑 SQL,然后将其转换为 EF 代码。如果你具体告诉它,你想要什么,你就会得到它。

【讨论】:

  • 我返回并按照此答案中的建议重新编码查询,尽管查询要长得多,但由于我必须在 Quote 中拼出每个属性,它执行在 0.041-0.046 秒内(由 LINQPad 测量)。旧查询在 1.13 秒内执行。感谢你的回答。现在我看了看,这样做非常有意义,但我仍然认为 EF Core 可以通过旧查询解决这个问题。
  • 对上述答案的另一条评论。没有必要调用 AsEnumerable()。事实上,调用会消除异步查询的能力。
  • 很高兴,我能帮上忙。我已经重写了很多 Linq-to-Sql 查询,我知道通过优化可以实现什么:-)
  • 如果您需要异步查询,只需调用.ToListAsync() 而不是.AsEnumerable()。它会产生同样的效果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-09
相关资源
最近更新 更多