Sto cercando di implementare un LEFT OUTER JOIN in Linq contro un DbContext di Entity Framework Core 2.0. È importante che la query venga tradotta in SQL, piuttosto che valutata localmente. Ho esaminato diverse soluzioni StackOverflow tra cui questa che è buona, ma nessuna utilizza EF Core.
Il problema che ho riscontrato è che EF Core restituisce il seguente avviso / errore per il metodo DefaultIfEmpty()
:
The LINQ expression 'DefaultIfEmpty()' could not be translated and will be evaluated locally
Senza il metodo DefaultIfEmpty()
viene utilizzato un JOIN INTERNO. La mia query LINQ si presenta così:
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
});
Qualcuno conosce un modo alternativo?
OK, questo è il mio errore, basato su un commento in un'altra domanda SO che ha rilevato che DefaultIfEmpty()
è necessario per rendere la query un OUTER JOIN. Guardando l'SQL sottostante, un SINISTRO SINISTRO viene inviato al database quando rimuovo la specifica DefaultIfEmpty()
. Non sono sicuro se questo differisce dal fare un SINISTRA SINISTRA rispetto alle raccolte in memoria, ma ha risolto il mio problema.
L'SQL generato da EF Core per questa query di Linq si presenta così:
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: trovato il tempo di provare questo e @Ivan Stoev è corretto: se le proprietà di navigazione sono correttamente impostate nella definizione del contesto EF, EF genererà il SINISTRA SINISTRA. Questo è un approccio migliore quando si usa EF.
Proprietà di navigazione EF sul Portfolio:
public virtual ICollection<BankAccount> BankAccounts { get; set; }
Query LINQ tramite la proprietà di navigazione:
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
});
Codice SQL risultante:
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))
Si noti che il rilascio di DefaultIfEmpty()
dalla query LINQ risulta in UN INTERNO INNER.