实体框架核心生成奇怪的SQL

时间:2019-03-15 13:28:05

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

我有一个正在尝试优化的现有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会更改其名称行为,只需添加其他组值即可。有人可以解释这种行为吗?

1 个答案:

答案 0 :(得分:0)

让我们退后一步,从不同的角度看待它:如果您要手动编写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代码。如果您明确地说出想要的东西,那么您会得到它的。