Имеется таблица Tickets, у которой несколько связей. Чтобы загрузить все необходимые связанные данные, получается такой запрос:
var ticket = _db.Tickets
.Include(t=>t.Group)
.Include(t=>t.TicketContacts)
.ThenInclude(t=>t.Contact)
.Include(t=>t.TicketProducts)
.ThenInclude(t=>t.Product)
.Include(t=>t.AppUser)
.Include(t => t.Status)
.Include(t => t.Abonent)
.Include(t=>t.Events)
.ThenInclude(t=>t.Message)
.ThenInclude(t=>t.Attachments)
.Include(t => t.Events)
.ThenInclude(t => t.Message)
.ThenInclude(t => t.Contact)
.Include(t=>t.Events)
.ThenInclude(t=>t.User)
.FirstOrDefault(t => t.Id == id);
Настораживает большое количество include'ов. В бд будут отправляться несколько запросов. Правильно ли я делаю, или можно как то это упростить ?
Как я понял (по окну Вывода), получаю 5 запросов:
SELECT TOP(1) [t].[Id], ...
FROM[Tickets] AS[t]
LEFT JOIN[Abonents] AS[t.Abonent] ON[t].[AbonentId] = [t.Abonent].[Id]
INNER JOIN[Statuses] AS[t.Status] ON[t].[StatusId] = [t.Status].[Id]
LEFT JOIN[AspNetUsers] AS[t.AppUser] ON[t].[AppUserId] = [t.AppUser].[Id]
LEFT JOIN[Groups] AS[t.Group] ON[t].[GroupId] = [t.Group].[Id]
WHERE[t].[Id] = @__id_0
ORDER BY[t].[Id]
SELECT[t.TicketContacts].[TicketId], ...
FROM[TicketContacts] AS[t.TicketContacts]
INNER JOIN[Contacts] AS[t.Contact] ON[t.TicketContacts].[ContactId] = [t.Contact].[Id]
INNER JOIN(
SELECT DISTINCT [t1].*
FROM (
SELECT TOP(1) [t0].[Id]
FROM[Tickets] AS[t0]
LEFT JOIN[Abonents] AS[t.Abonent0] ON[t0].[AbonentId] = [t.Abonent0].[Id]
INNER JOIN[Statuses] AS[t.Status0] ON[t0].[StatusId] = [t.Status0].[Id]
LEFT JOIN[AspNetUsers] AS[t.AppUser0] ON[t0].[AppUserId] = [t.AppUser0].[Id]
LEFT JOIN[Groups] AS[t.Group0] ON[t0].[GroupId] = [t.Group0].[Id]
WHERE[t0].[Id] = @__id_0
ORDER BY[t0].[Id]
) AS[t1]
) AS[t2] ON[t.TicketContacts].[TicketId] = [t2].[Id]
ORDER BY[t2].[Id]
SELECT[t.TicketProducts].[TicketId], ...
FROM[TicketProducts] AS[t.TicketProducts]
INNER JOIN[Products] AS[t.Product] ON[t.TicketProducts].[ProductId] = [t.Product].[Id]
INNER JOIN(
SELECT DISTINCT [t4].*
FROM (
SELECT TOP(1) [t3].[Id]
FROM[Tickets] AS[t3]
LEFT JOIN[Abonents] AS[t.Abonent1] ON[t3].[AbonentId] = [t.Abonent1].[Id]
INNER JOIN[Statuses] AS[t.Status1] ON[t3].[StatusId] = [t.Status1].[Id]
LEFT JOIN[AspNetUsers] AS[t.AppUser1] ON[t3].[AppUserId] = [t.AppUser1].[Id]
LEFT JOIN[Groups] AS[t.Group1] ON[t3].[GroupId] = [t.Group1].[Id]
WHERE[t3].[Id] = @__id_0
ORDER BY[t3].[Id]
) AS[t4]
) AS[t5] ON[t.TicketProducts].[TicketId] = [t5].[Id]
ORDER BY[t5].[Id]
SELECT[t.Events].[Id], ...
FROM[TicketEvents] AS[t.Events]
LEFT JOIN[AspNetUsers] AS[t.User] ON[t.Events].[AppUserId] = [t.User].[Id]
LEFT JOIN[Messages] AS[t.Message] ON[t.Events].[MessageId] = [t.Message].[Id]
LEFT JOIN[Contacts] AS[t.Message.Contact] ON[t.Message].[ContactId] = [t.Message.Contact].[Id]
INNER JOIN(
SELECT DISTINCT [t7].*
FROM (
SELECT TOP(1) [t6].[Id]
FROM[Tickets] AS[t6]
LEFT JOIN[Abonents] AS[t.Abonent2] ON[t6].[AbonentId] = [t.Abonent2].[Id]
INNER JOIN[Statuses] AS[t.Status2] ON[t6].[StatusId] = [t.Status2].[Id]
LEFT JOIN[AspNetUsers] AS[t.AppUser2] ON[t6].[AppUserId] = [t.AppUser2].[Id]
LEFT JOIN[Groups] AS[t.Group2] ON[t6].[GroupId] = [t.Group2].[Id]
WHERE[t6].[Id] = @__id_0
ORDER BY[t6].[Id]
) AS[t7]
) AS[t8] ON[t.Events].[TicketId] = [t8].[Id]
ORDER BY[t8].[Id], [t.Message].[Id]
SELECT[t.Message.Attachments].[Id], ...
FROM[Attachments] AS[t.Message.Attachments]
INNER JOIN(
SELECT DISTINCT [t.Message0].[Id], [t11].[Id] AS [Id0]
FROM[TicketEvents] AS [t.Events0]
LEFT JOIN [AspNetUsers] AS[t.User0] ON [t.Events0].[AppUserId] = [t.User0].[Id]
LEFT JOIN [Messages] AS[t.Message0] ON [t.Events0].[MessageId] = [t.Message0].[Id]
LEFT JOIN [Contacts] AS[t.Message.Contact0] ON [t.Message0].[ContactId] = [t.Message.Contact0].[Id]
INNER JOIN (
SELECT DISTINCT [t10].*
FROM (
SELECT TOP(1) [t9].[Id]
FROM[Tickets] AS[t9]
LEFT JOIN[Abonents] AS[t.Abonent3] ON[t9].[AbonentId] = [t.Abonent3].[Id]
INNER JOIN[Statuses] AS[t.Status3] ON[t9].[StatusId] = [t.Status3].[Id]
LEFT JOIN[AspNetUsers] AS[t.AppUser3] ON[t9].[AppUserId] = [t.AppUser3].[Id]
LEFT JOIN[Groups] AS[t.Group3] ON[t9].[GroupId] = [t.Group3].[Id]
WHERE[t9].[Id] = @__id_0
ORDER BY[t9].[Id]
) AS[t10]
) AS[t11] ON[t.Events0].[TicketId] = [t11].[Id]
) AS[t12] ON[t.Message.Attachments].[MessageId] = [t12].[Id]
ORDER BY[t12].[Id0], [t12].[Id]
Всё наоборот: .Include()
требуются, чтобы уменьшить число запросов.
В классическом EF дело обстояло так. Если вы сначала получите список Tickets
, потом попросите у БД ticket.TicketContacts
и т.д., то выполнится два запроса: SELECT * FROM Tickets
и SELECT * FROM TicketContacts WHERE TICKET_ID = @ticketId
(условно)
Если же вызовете Tickets.Include(t=>t.TicketContacts)
, то выполнится единственный запрос SELECT T.*, G.* FROM Tickets T LEFT JOIN TicketContacts G ON G.TICKET_ID = T.ID
(опять же, условно. скорее всего будут перечислены поля и вообще)
В EF.Core, если мне не изменяет память, возможен только второй вариант: автоматическая ленивая загрузка не реализована.
Так что вы поступаете абсолютно правильно. Объединение таблиц - то, для чего созданы серверы реляционных БД, пусть они этим и занимаются.
UPD: В документации MS написано, что объединение в единственный запрос - фича EF Core 3, а в версии 2 могло отправиться несколько запросов. По той же ссылке рекомендован пакет, который включает ленивую загрузку в EF Core (но тогда точно запросов будет много).
Если у вас крупное реальное enterprise приложение, то да, иногда приходится писать весьма сложную логику запросов и не всегда даже linq'шных, а приходится писать крупные полотна чистого SQL.
Я не уверен, но мне кажется что вы не совсем правы насчёт того, что будут выполняться отдельные запросы -- потому что насколько я читал про core (сам не часто сталкивался на практике) иногда может делать JOIN а иногда и отдельный запрос. А вот EF6 как раз нагородит огромный, сложный но точно один запрос.
Варианты упрощения тут особо не просматриваются -- у всё include и then include.
Максимум -- можете попробовать перейти на ОРМ Dapper, что даст вам возможность писать raw sql запросы в режиме multiple results, иногда это оказывается полезнее, чем тащить громоздкий linq.
Я пробовал скрестить dapper с EF, вполне получается. Примеры с multiple results можете поискать не только в документации, но и на so бывает, вот например: Как получить связанные сущности на даппере?
Эванс в книге «Предметно-ориентированное проектирование» рекомендует опираться на понятие агрегата. Предполагается, что сущность-агрегат может содержать другие сущности, которые без неё не имеют смысла и не могут быть доступны.
Классический пример — сущность Заказ с вложенными Позициями заказа.
В то же время, если сущность должна быть доступна отдельно в каких-то сценариях, то она не может быть вложенной. Это отдельный агрегат.
Возможно, у вас эта ситуация. Возможно, сообщения (message) и пользователи (user) могут быть доступны вне контекста тикета (ticket).
В целом, несколько Include
это вполне нормально, но у вас их как-то и правда много.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
У меня есть 4 фигуры (соответственно 4 класса) и нужно каждой фигуре ввести свои значения, чтобы подсчитать площадьНужно реализовать ввод...
Всё происходит на Денвере Файл находящийся в папке css при css коде: 'background: url(/images/sprite
Есть простой <span title="заполните поле">*</span>