简体   繁体   中英

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

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?

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM