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

.net-core c# entity-framework-core lambda linq

Question

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?

1
0
3/29/2018 10:03:47 AM

Accepted Answer

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.

7
4/10/2018 1:46:55 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow