Linq: объединение с включением / последующим включением в Entity Framework Core - PullRequest
0 голосов
/ 03 мая 2020

Я работаю над проектом с SQL Сервером и EF Core v3.

У меня есть 4 таблицы, связанные друг с другом. Вот мои схемы таблиц:

enter image description here

Я написал 2 запроса Linq к этим таблицам - один из них использовал соединение следующим образом:

var result = (from emailTemplate in _context.EmailTemplates.Include(et => et.EmailTemplateContents)
              join priorityLookup in _context.Lookups on new { GroupKey = "PRIORITIES", DetailKey = emailTemplate.Priority, emailTemplate.CompanyId } equals new { priorityLookup.GroupKey, priorityLookup.DetailKey, priorityLookup.CompanyId }
              join statusLookup in _context.Lookups on new { GroupKey = "STATUSES", DetailKey = emailTemplate.StatusCode, emailTemplate.CompanyId } equals new { statusLookup.GroupKey, statusLookup.DetailKey, statusLookup.CompanyId }
              join priorityLookupLabel in _context.LookupLabels on new { Locale = 1033, priorityLookup.DetailKey, priorityLookup.CompanyId } equals new { priorityLookupLabel.Locale, priorityLookupLabel.DetailKey, priorityLookupLabel.CompanyId }
              join statusLookupLabel in _context.LookupLabels on new { Locale = 1033, statusLookup.DetailKey, statusLookup.CompanyId } equals new { statusLookupLabel.Locale, statusLookupLabel.DetailKey, statusLookupLabel.CompanyId }
              where emailTemplate.CompanyId == 3
              select new EmailTemplateModel
                      {
                          Code = emailTemplate.Code,
                          TemplateName = emailTemplate.TemplateName,
                          FromEmail = emailTemplate.FromEmail,
                          BccEmail = emailTemplate.BccEmail,
                          CcEmail = emailTemplate.CcEmail,
                          PriorityCode = emailTemplate.Priority,
                          Priority = priorityLookupLabel.Label,
                          Subject = emailTemplate.EmailTemplateContents.Subject,
                          Body = HttpUtility.HtmlDecode(emailTemplate.EmailTemplateContents.Body),
                          StatusCode = emailTemplate.StatusCode,
                          Status = statusLookupLabel.Label,
                          ToEmail = emailTemplate.ToEmail,
                          TriggerSqlCommand = emailTemplate.TriggerSqlCommand,
                          TriggerType = emailTemplate.TriggerType,
                          ModifDate = emailTemplate.ModifDate
                      }).ToList();

и один из них использует .Include примерно так:

var results = _context.EmailTemplates
                      .Where(e => e.CompanyId == 3)
                      .Include(e => e.EmailTemplateContents)
                      .Include(e => e.Lookups)
                        .ThenInclude(g => g.LookupLabels)
                      .Include(e => e.LookupsNavigation)
                        .ThenInclude(g => g.LookupLabels)
                      .Select(e => new EmailTemplateModel
                                   {
                                       Code = e.Code,
                                       TemplateName = e.TemplateName,
                                       FromEmail = e.FromEmail,
                                       BccEmail = e.BccEmail,
                                       CcEmail = e.CcEmail,
                                       PriorityCode = e.Priority,
                                       PriorityLabel = e.Lookups.LookupLabels.FirstOrDefault(l => l.Locale == 1033),
                                       Subject = e.EmailTemplateContents.Subject,
                                       Body = HttpUtility.HtmlDecode(e.EmailTemplateContents.Body),
                                       StatusCode = e.StatusCode,
                                       StatusLabel = e.LookupsNavigation.LookupLabels.FirstOrDefault(l => l.Locale == 1033),
                                       ToEmail = e.ToEmail,
                                       TriggerSqlCommand = e.TriggerSqlCommand,
                                       TriggerType = e.TriggerType,
                                       ModifDate = e.ModifDate
                                   }).ToList();

Я попытался понять, есть ли разница в производительности между этими двумя типами, поэтому я проверил запрос, сгенерированный EF, с помощью профилировщика.

Сценарий SQL, созданный из оператора Join:

SELECT 
    [e].[Code], [e].[TemplateName], [e].[FromEmail], [e].[BccEmail], 
    [e].[CcEmail], [e].[Priority], [l1].[Label], 
    [e0].[Subject], [e0].[Body], [e].[StatusCode], [l2].[Label], 
    [e].[ToEmail], [e].[TriggerSqlCommand], [e].[TriggerType], 
    [e].[ModifDate]
FROM [EmailTemplates] AS [e]
INNER JOIN [Lookups] AS [l] ON (('PRIORITIES' = [l].[GroupKey]) AND ([e].[Priority] = [l].[DetailKey])) AND ([e].[CompanyId] = [l].[CompanyId])
INNER JOIN [Lookups] AS [l0] ON (('STATUSES' = [l0].[GroupKey]) AND ([e].[StatusCode] = [l0].[DetailKey])) AND ([e].[CompanyId] = [l0].[CompanyId])
INNER JOIN [LookupLabels] AS [l1] ON ((1033 = [l1].[Locale]) AND ([l].[DetailKey] = [l1].[DetailKey])) AND ([l].[CompanyId] = [l1].[CompanyId])
INNER JOIN [LookupLabels] AS [l2] ON ((1033 = [l2].[Locale]) AND ([l0].[DetailKey] = [l2].[DetailKey])) AND ([l0].[CompanyId] = [l2].[CompanyId])
LEFT JOIN [EmailTemplateContents] AS [e0] ON ([e].[CompanyId] = [e0].[CompanyId]) AND ([e].[Code] = [e0].[EmailTemplateCode])
WHERE [e].[CompanyId] = 3

Сценарий SQL, созданный из оператора .Include:

SELECT 
    [e].[Code], [e].[TemplateName], [e].[FromEmail], [e].[BccEmail], 
    [e].[CcEmail], [e].[Priority], [t0].[DetailKey], [t0].[CompanyId], 
    [t0].[Locale], [t0].[Label], [t0].[OrderNo], [e0].[Subject], [e0].[Body], 
    [e].[StatusCode], [t2].[DetailKey], [t2].[CompanyId], [t2].[Locale], 
    [t2].[Label], [t2].[OrderNo], [e].[ToEmail], [e].[TriggerSqlCommand], 
    [e].[TriggerType], [e].[ModifDate]
FROM [EmailTemplates] AS [e]
INNER JOIN [Lookups] AS [l] ON ([e].[Priority] = [l].[DetailKey]) AND ([e].[CompanyId] = [l].[CompanyId])
LEFT JOIN [EmailTemplateContents] AS [e0] ON ([e].[CompanyId] = [e0].[CompanyId]) AND ([e].[Code] = [e0].[EmailTemplateCode])
INNER JOIN [Lookups] AS [l0] ON ([e].[StatusCode] = [l0].[DetailKey]) AND ([e].[CompanyId] = [l0].[CompanyId])
LEFT JOIN (
    SELECT [t].[DetailKey], [t].[CompanyId], [t].[Locale], [t].[Label], [t].[OrderNo]
    FROM (
        SELECT [l1].[DetailKey], [l1].[CompanyId], [l1].[Locale], [l1].[Label], [l1].[OrderNo], ROW_NUMBER() OVER(PARTITION BY [l1].[DetailKey], [l1].[CompanyId] ORDER BY [l1].[DetailKey], [l1].[CompanyId], [l1].[Locale]) AS [row]
        FROM [LookupLabels] AS [l1]
        WHERE [l1].[Locale] = 1033
    ) AS [t]
    WHERE [t].[row] <= 1
) AS [t0] ON ([l].[DetailKey] = [t0].[DetailKey]) AND ([l].[CompanyId] = [t0].[CompanyId])
LEFT JOIN (
    SELECT [t1].[DetailKey], [t1].[CompanyId], [t1].[Locale], [t1].[Label], [t1].[OrderNo]
    FROM (
        SELECT [l2].[DetailKey], [l2].[CompanyId], [l2].[Locale], [l2].[Label], [l2].[OrderNo], ROW_NUMBER() OVER(PARTITION BY [l2].[DetailKey], [l2].[CompanyId] ORDER BY [l2].[DetailKey], [l2].[CompanyId], [l2].[Locale]) AS [row]
        FROM [LookupLabels] AS [l2]
        WHERE [l2].[Locale] = 1033
    ) AS [t1]
    WHERE [t1].[row] <= 1
) AS [t2] ON ([l0].[DetailKey] = [t2].[DetailKey]) AND ([l0].[CompanyId] = [t2].[CompanyId])
WHERE [e].[CompanyId] = 3

I сравнил фактические планы выполнения для обоих, чтобы увидеть, в чем разница между этими двумя.

Вот план выполнения соединения:

Join Actual Execution Plan

и вот план выполнения включения:

Include Actual Execution Plan

Стоимость обоих запросов равна 50%.

Теперь у меня есть пара вопросов:

  1. Исходя из стоимости запроса (50%), следует ли мне рассматривать эти два показателя одинаково с точки зрения производительности?
  2. Есть ли какие-либо предложения по использованию include или join, чтобы сделать один из них быстрее или с меньшими затратами?
  3. Каковы преимущества и недостатки использования Join (синтаксис / обслуживание)?
  4. Каковы плюсы и минусы использования Include (синтаксис / обслуживание)?
  5. Какой из них следует использовать, если в таблице несколько записей или много записей?
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...