У меня есть запрос 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 изменил бы его поведение просто путем добавления дополнительных значений группы. Может кто-нибудь объяснить поведение?