Unione esterna sinistra da LINQ a SQL


140

Questa query è equivalente a un LEFT OUTERjoin?

//assuming that I have a parameter named 'invoiceId' of type int
from c in SupportCases
let invoice = c.Invoices.FirstOrDefault(i=> i.Id == invoiceId)
where (invoiceId == 0 || invoice != null)    
select new 
{
      Id = c.Id
      , InvoiceId = invoice == null ? 0 : invoice.Id
}

Risposte:


167

Non del tutto - poiché ogni riga "sinistra" in un join esterno-sinistro corrisponderà a righe 0-n "destra" (nella seconda tabella), dove -come il tuo corrisponde solo a 0-1. Per eseguire un join esterno sinistro, è necessario SelectManye DefaultIfEmpty, ad esempio:

var query = from c in db.Customers
            join o in db.Orders
               on c.CustomerID equals o.CustomerID into sr
            from x in sr.DefaultIfEmpty()
            select new {
               CustomerID = c.CustomerID, ContactName = c.ContactName,
               OrderID = x == null ? -1 : x.OrderID };   

( o tramite i metodi di estensione )


19
Qualcuno può spiegare come funziona questa sintassi pazza? Non riesco a vedere come una di quelle parole chiave magicamente la renda un join sinistro. Cosa fa "into sr"? A volte Linq mi frustra :)
Joe Phillips,

2
@JoePhillips Ho molta esperienza SQL, ma cercare di imparare LINQ è come guadare fango. Sono d'accordo che è assolutamente pazzo.
Nick.McDermaid,

@ marc-Gravell: Potrebbe aiutarmi a risolvere il mio query SQL per la conversione LINQ: stackoverflow.com/questions/28367941/...
Vishal I Patil

@VishalIPatil perché vuoi convertire da SQL a LINQ? SQL funziona bene ed è molto più prevedibile ed efficiente ...
Marc Gravell

1
@VishalIPatil quindi ... perché farlo? Quasi tutti gli strumenti LINQ includono la possibilità di eseguire SQL scritti a mano. Perché non farlo?
Marc Gravell

216

Non hai bisogno delle dichiarazioni into:

var query = 
    from customer in dc.Customers
    from order in dc.Orders
         .Where(o => customer.CustomerId == o.CustomerId)
         .DefaultIfEmpty()
    select new { Customer = customer, Order = order } 
    //Order will be null if the left join is null

E sì, la query sopra crea effettivamente un join LEFT OUTER.

Collegamento a una domanda simile che gestisce più join a sinistra: Linq a Sql: più join esterni a sinistra


14
Mentre so che la risposta di @Marc Gravvel funziona, preferisco davvero questo metodo perché IMO sembra più in linea con l'aspetto di un join sinistro.
llaughlin,

1
Risposta eccellente. Alla ricerca di più di 5 ore di ricerca su Google. Questo è l'unico modo in cui SQL risultante avrà lasciato il join in esso.
Faisal Mq,

1
GRAZIE moltissimo .... Stavo cercando una soluzione per tutto questo pomeriggio e il tuo codice l'ha inchiodato (e sembra naturale avviarsi). Vorrei poter votare più volte.
Jim,

2
@Jim grazie :-) Sono contento che gli sviluppatori stiano ancora ottenendo miglia per questa risposta. Concordo pienamente sul fatto che DefaultIfEmpty () sia molto più naturale dell'uso delle istruzioni into.
Amir,

7
Solo una nota per chiunque lo trovi come ho appena fatto, questo si traduce in un JOIN ESTERNO SINISTRO all'interno di una CROSS APPLY, il che significa che otterrai duplicati se ci sono più corrispondenze sul lato destro del join. La soluzione di Marc Gravell, sebbene non così "carina", mi ha dato l'output SQL e il set di risultati corretti che stavo cercando.
Mike U,


5

Ho trovato 1 soluzione. se vuoi tradurre questo tipo di SQL (join sinistro) in Linq Entity ...

SQL:

SELECT * FROM [JOBBOOKING] AS [t0]
LEFT OUTER JOIN [REFTABLE] AS [t1] ON ([t0].[trxtype] = [t1].[code])
                                  AND ([t1]. [reftype] = "TRX")

LINQ:

from job in JOBBOOKINGs
join r in (from r1 in REFTABLEs where r1.Reftype=="TRX" select r1) 
          on job.Trxtype equals r.Code into join1
from j in join1.DefaultIfEmpty()
select new
{
   //cols...
}

Vedi questo commento , le entità Linq-to-SQL non supportano DefaultIfEmpty.
TJ Crowder,

2

Vorrei aggiungere un'altra cosa. In LINQ to SQL se il tuo DB è costruito correttamente e le tue tabelle sono correlate tramite vincoli di chiave esterna, non è necessario eseguire un join.

Utilizzando LINQPad ho creato la seguente query LINQ:

//Querying from both the CustomerInfo table and OrderInfo table
from cust in CustomerInfo
where cust.CustomerID == 123456
select new {cust, cust.OrderInfo}

Che è stato tradotto nella query (leggermente troncata) di seguito

 -- Region Parameters
 DECLARE @p0 Int = 123456
-- EndRegion
SELECT [t0].[CustomerID], [t0].[AlternateCustomerID],  [t1].[OrderID], [t1].[OnlineOrderID], (
    SELECT COUNT(*)
    FROM [OrderInfo] AS [t2]
    WHERE [t2].[CustomerID] = [t0].[CustomerID]
    ) AS [value]
FROM [CustomerInfo] AS [t0]
LEFT OUTER JOIN [OrderInfo] AS [t1] ON [t1].[CustomerID] = [t0].[CustomerID]
WHERE [t0].[CustomerID] = @p0
ORDER BY [t0].[CustomerID], [t1].[OrderID]

Si noti quanto LEFT OUTER JOINsopra.


1

Prenditi cura delle prestazioni:

Ho riscontrato che almeno con EF Core le diverse risposte fornite qui potrebbero comportare prestazioni diverse. Sono consapevole che l'OP ha chiesto di Linq a SQL, ma mi sembra che le stesse domande si verifichino anche con EF Core.

In un caso specifico che ho dovuto affrontare, il suggerimento (sintatticamente più gradevole) di Marc Gravell ha comportato l'applicazione di join a sinistra all'interno di una croce - analogamente a quanto descritto da Mike U - che ha prodotto il risultato che i costi stimati per questa specifica query erano due volte più elevato rispetto a una query senza join incrociati . I tempi di esecuzione del server differivano di un fattore 3 . [1]

La soluzione di Marc Gravell ha portato a una query senza cross-join.

Contesto: avevo essenzialmente bisogno di eseguire due join a sinistra su due tabelle, ognuno dei quali richiedeva nuovamente un join a un'altra tabella. Inoltre, lì ho dovuto specificare altre condizioni dove sui tavoli su cui avevo bisogno di applicare il join sinistro. Inoltre, avevo due join interni sul tavolo principale.

Costi stimati dell'operatore:

  • con croce applicare: 0,2534
  • senza croce applicare: 0.0991.

Tempi di esecuzione del server in ms (query eseguite 10 volte; misurate utilizzando SET STATISTICS TIME ON):

  • con croce applicare: 5, 6, 6, 6, 6, 6, 6, 6, 6, 6
  • senza croce applicare: 2, 2, 2, 2, 2, 2, 2, 2, 2, 2

(La prima esecuzione è stata più lenta per entrambe le query; sembra che qualcosa sia memorizzato nella cache.)

Tabella dimensioni:

  • tabella principale: 87 file,
  • prima tabella per join sinistro: 179 righe;
  • seconda tabella per join sinistro: 7 righe.

Versione EF Core: 2.2.1.

Versione di SQL Server: MS SQL Server 2017 - 14 ... (su Windows 10).

Tutte le tabelle pertinenti avevano indici solo sulle chiavi primarie.

La mia conclusione: si consiglia sempre di guardare l'SQL generato poiché può davvero differire.


[1] È interessante notare che, impostando le "Statistiche client" in MS SQL Server Management Studio, ho potuto vedere una tendenza opposta; vale a dire che l'ultima esecuzione della soluzione senza applicazione incrociata ha richiesto più di 1 secondo. Suppongo che qui qualcosa non andasse, forse con la mia configurazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.