How can I implement a LEFT OUTER JOIN in LINQ using lambda syntax on Entity Framework Core 2.0?

17,517

OK, this is my mistake, based on a comment in another SO question that noted that DefaultIfEmpty() is necessary to make the query an OUTER JOIN. Looking at the underlying SQL, a LEFT JOIN is being submitted to the database when I remove the DefaultIfEmpty() specification. I'm not sure if this differs from doing a LEFT JOIN over in-memory collections, but it has solved my problem.

The SQL as generated by EF Core for this Linq query looks like this:

SELECT [p].[ID], 
       [bnk].[BankAccountNumber] AS [BankAccount], 
       [bnk].[BeneficiaryReference] AS [BankRef], 
       [p].[Code], 
       [p].[DisplayName] AS [Description]
    FROM [Portfolio] AS [p]
    LEFT JOIN [BankAccount] AS [bnk] ON [p].[ID] = [bnk].[PortfolioId]
WHERE (([p].[IsActive] = 1)))

EDIT: Found time to test this out and @Ivan Stoev is correct: if your navigation properties are correctly setup in the EF context definition, EF will generate the LEFT JOIN. This is a better approach when using EF.

EF navigation property on Portfolio:

public virtual ICollection<BankAccount> BankAccounts { get; set; }

LINQ query via navigation property:

var join = context.Portfolios
                  .Where(p => p.IsActive)
                  .SelectMany(p => p.BankAccounts.DefaultIfEmpty(), (p, b) => new
                                                {
                                                    Id = p.Id,
                                                    BankAccount = b.BankAccountNumber,
                                                    BankRef = b.BeneficiaryReference,
                                                    Code = p.Code,
                                                    Description = p.DisplayName
                                                });

Resulting SQL code:

SELECT [p].[ID], [p.BankAccounts].[BankAccountNumber] AS [BankAccount], [p.BankAccounts].[BeneficiaryReference] AS [BankRef], [p].[Code], [p].[DisplayName] AS [Description]
    FROM [core].[Portfolio] AS [p]
    LEFT JOIN [ims].[BankAccount] AS [p.BankAccounts] ON [p].[ID] = [p.BankAccounts].[PortfolioId]
WHERE (([p].[IsActive] = 1))

Note that dropping the DefaultIfEmpty() from the LINQ query results in an INNER JOIN.

Share:
17,517
Peter
Author by

Peter

I have 20 years of experience in software architecture and development; I am passionate about extending and sharing my knowledge and leading technical teams and I also have strong computer security, systems integration and optimisation training and experience. Most of my career has been spent in the finance sector, and asset management in particular, which built on the knowledge I gained doing a commerce degree. Specialties: My main technical experience is in .NET and SQL server architecture, design and development, system integration and system optimisation. Recently I have broadened my interests, deploying Ubuntu VMs on AWS to run websites and developing REST API with Java and Scala on the JVM, primarily to integrate with Google and Payment systems. I am currently building REST APIs in .NET Core and Java on Ubuntu, using Docker for deployment. In my "spare time" I am integrating Java/Dropbox Web APIs with temperature monitoring software I am running on Raspberry Pi's to monitor fridge temperatures at clinics.

Updated on June 24, 2022

Comments

  • Peter
    Peter almost 2 years

    I am trying to implement a LEFT OUTER JOIN in Linq against an Entity Framework Core 2.0 DbContext. It's important that the query is translated to SQL, rather than evaluated locally. I've reviewed several StackOverflow solutions including this one which is good, but none are using EF Core.

    The problem I get is that EF Core returns the following warning/error for the DefaultIfEmpty() method:

    The LINQ expression 'DefaultIfEmpty()' could not be translated and will be evaluated locally

    Without the DefaultIfEmpty() method an INNER JOIN is used. My LINQ query looks like this:

    var join = context.Portfolios
               .Where(p => p.IsActive)
               .GroupJoin(context.BankAccounts, 
                          prt => prt.Id, 
                          bnk => bnk.PortfolioId, 
                          (prt, bnks) => new {Portfolio=prt,Account=bnks.DefaultIfEmpty()})
               .SelectMany(r => r.Accounts.DefaultIfEmpty(),
                           (p, b) => new 
                               {
                                   Id = p.Portfolio.Id,
                                   BankAccount = b.BankAccountNumber,
                                   BankRef = b.BeneficiaryReference,
                                   Code = p.Portfolio.Code,
                                   Description = p.Portfolio.DisplayName
                               });
    

    Does anyone know a way around this?