5

I am working on a project with SQL Server and EF Core v3.

I have 4 tables related to each other. Here are my tables schemes:

enter image description here

I wrote 2 Linq queries against those tables - one of them using join like this:

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();

and one of them using .Include like so :

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();

I tried to understand if there is any performance difference between these two type, so I checked the query generated by EF using profiler.

The SQL Script generated from the Join statement is :

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

The SQL script generated from the .Include statement is :

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 compared the actual execution plans for both to see what the difference between these two is.

Here is the join execution plan:

Join Actual Execution Plan

and here is the include execution plan:

Include Actual Execution Plan

The cost of both queries are the same 50%.

Now I have couple of questions :

  1. Based on the query cost(50%) should I consider these two equal performance-wise ?
  2. Is there any suggestion for using include or join to make one of them faster or with fewer cost?
  3. What are the pros and cons of using Join (Syntax/maintenance)?
  4. What are the pros and cons of using Include (Syntax/maintenance)?
  5. Which one should I use if the table has a few records or if it has lots of records?
11
  • 1
    As far as i remember, you don't need all the Include's in the second one since you are already selecting the properties. Not sure that it will generate the same SQL though. Commented May 3, 2020 at 1:33
  • Also not sure that both queries will always generate the same results since the first one uses more constraints on joins than second one (see priorityLookup and statusLookup in the first one). Commented May 3, 2020 at 1:37
  • thanks for your comment since the EmailTemplate table has two foreign keys to the Lookup table I have 2 Navigation property I guess I should include both navigation property in order to get Child Values for each one of them am I right? Commented May 3, 2020 at 1:43
  • the results for both are identical the reason I used more constraints in the join statement is I don't want to use navigation property Commented May 3, 2020 at 2:05
  • I'm writing about GroupKey = "PRIORITIES" and GroupKey = "STATUSES" in the first version. Commented May 3, 2020 at 9:25

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.