我有一个正在尝试优化的现有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会更改其名称行为,只需添加其他组值即可。有人可以解释这种行为吗?
答案 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代码。如果您明确地说出想要的东西,那么您会得到它的。