LINQ to SQL Left Outer Join

140

Entspricht diese Abfrage einem 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
}
Ali Kazmi
quelle

Antworten:

167

Nicht ganz - da jede "linke" Zeile in einem Links-Außen-Join mit 0-n "rechten" Zeilen (in der zweiten Tabelle) übereinstimmt, wobei Ihre nur mit 0-1 übereinstimmt. Um eine linke äußere Verknüpfung durchzuführen, benötigen Sie SelectManyund DefaultIfEmptyzum Beispiel:

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 };   

( oder über die Erweiterungsmethoden )

Marc Gravell
quelle
19
Kann jemand erklären, wie diese verrückte Syntax funktioniert? Ich kann nicht erkennen, wie eines dieser Schlüsselwörter es auf magische Weise zu einem Links-Join macht. Was macht das "into sr"? Linq frustriert mich manchmal :)
Joe Phillips
2
@ JoePhillips Ich habe viel SQL-Erfahrung, aber der Versuch, LINQ zu lernen, ist wie durch Schlamm zu waten. Ich stimme zu, dass es absolut verrückt ist.
Nick.McDermaid
@ Marc-Gravell: Könnten Sie mir bei der Lösung meiner SQL-Abfrage zu Linq-Konvertierung helfen
Vishal I Patil
@VishalIPatil Warum möchten Sie von SQL nach LINQ konvertieren? SQL funktioniert
einwandfrei
1
@ VishalIPatil also ... warum das? Nahezu jedes LINQ-Tool bietet die Möglichkeit, handgeschriebenes SQL auszuführen. Warum nicht einfach das tun?
Marc Gravell
216

Sie brauchen die into-Anweisungen nicht:

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

Und ja, die obige Abfrage erzeugt tatsächlich einen LEFT OUTER-Join.

Link zu einer ähnlichen Frage, die mehrere linke Verknüpfungen behandelt: Linq to Sql: Mehrere linke äußere Verknüpfungen

Amir
quelle
14
Obwohl ich weiß, dass die Antwort von @Marc Gravvel funktioniert, bevorzuge ich diese Methode wirklich, da sie sich IMO eher so anfühlt, wie ein linker Join aussehen sollte.
Laughlin
1
Hervorragende Antwort. Suchen Sie nach mehr als 5 Stunden Google-Suche. Dies ist die einzige Möglichkeit, mit der sich SQL ergeben hat.
Faisal Mq
1
Vielen Dank ... Ich habe den ganzen Nachmittag nach einer Lösung gesucht und Ihr Code hat es geschafft (und fühlt sich natürlich an). Ich wünschte, ich könnte dies mehrmals positiv bewerten.
Jim
2
@ Jim danke :-) Ich bin froh, dass Entwickler immer noch Meilen aus dieser Antwort ziehen. Ich stimme voll und ganz zu, dass sich DefaultIfEmpty () viel natürlicher anfühlt als die Verwendung der into-Anweisungen.
Amir
7
Nur eine Notiz für alle anderen, die dies so finden, wie ich es gerade getan habe. Dies führt zu einem LEFT OUTER JOIN in einer CROSS APPLY. Dies bedeutet, dass Sie Duplikate erhalten, wenn sich auf der rechten Seite des Joins mehrere Übereinstimmungen befinden. Marc Gravells Lösung, obwohl nicht so "hübsch", gab mir die richtige SQL-Ausgabe und Ergebnismenge, die ich suchte.
Mike U
13
Public Sub LinqToSqlJoin07()
Dim q = From e In db.Employees _
        Group Join o In db.Orders On e Equals o.Employee Into ords = Group _
        From o In ords.DefaultIfEmpty _
        Select New With {e.FirstName, e.LastName, .Order = o}

ObjectDumper.Write(q) End Sub

Überprüfen Sie http://msdn.microsoft.com/en-us/vbasic/bb737929.aspx

Krishnaraj Barvathaya
quelle
Netter Versuch, aber es sieht so aus, als würde das OP c # verwenden. Die VB-Syntax ist merkwürdig unterschiedlich.
Levitikon
5

Ich habe 1 Lösung gefunden. Wenn Sie diese Art von SQL (linker Join) in Linq Entity übersetzen möchten ...

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...
}
mokth
quelle
Siehe diesen Kommentar , Linq-to-SQL-Entitäten werden nicht unterstützt DefaultIfEmpty.
TJ Crowder
2

Ich möchte noch etwas hinzufügen. Wenn in LINQ to SQL Ihre Datenbank ordnungsgemäß erstellt wurde und Ihre Tabellen durch Fremdschlüsseleinschränkungen verknüpft sind, müssen Sie überhaupt keinen Join ausführen.

Mit LINQPad habe ich die folgende LINQ-Abfrage erstellt:

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

Welches wurde in die (leicht abgeschnittene) Abfrage unten übersetzt

 -- 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]

Beachten Sie die LEFT OUTER JOINoben genannten.

Brian Kraemer
quelle
1

Achten Sie auf die Leistung:

Ich habe festgestellt, dass zumindest bei EF Core die hier gegebenen unterschiedlichen Antworten zu einer unterschiedlichen Leistung führen können. Ich bin mir bewusst, dass das OP nach Linq to SQL gefragt hat, aber es scheint mir, dass die gleichen Fragen auch bei EF Core auftreten.

In einem bestimmten Fall, den ich behandeln musste, führte der (syntaktisch schönere) Vorschlag von Marc Gravell dazu, dass Linksverknüpfungen innerhalb eines Kreuzes angewendet wurden - ähnlich wie von Mike U beschrieben -, was dazu führte, dass die geschätzten Kosten für diese spezielle Abfrage zwei waren mal so hoch im Vergleich zu einer Abfrage ohne Cross-Joins . Die Serverausführungszeiten unterschieden sich um den Faktor 3 . [1]

Die Lösung von Marc Gravell führte zu einer Abfrage ohne Cross-Joins.

Kontext: Ich musste im Wesentlichen zwei Linksverknüpfungen für zwei Tabellen ausführen, von denen jede wiederum eine Verknüpfung mit einer anderen Tabelle erforderte. Außerdem musste ich dort andere Where-Bedingungen in den Tabellen angeben, auf die ich den linken Join anwenden musste. Außerdem hatte ich zwei innere Verknüpfungen auf dem Haupttisch.

Geschätzte Betreiberkosten:

  • mit Kreuz anwenden: 0,2534
  • ohne Kreuz zutreffen: 0,0991.

Serverausführungszeiten in ms (Abfragen werden 10 Mal ausgeführt; gemessen mit SET STATISTICS TIME ON):

  • mit Kreuz anwenden: 5, 6, 6, 6, 6, 6, 6, 6, 6, 6
  • ohne Kreuz anwenden: 2, 2, 2, 2, 2, 2, 2, 2, 2, 2

(Der allererste Lauf war für beide Abfragen langsamer; anscheinend wird etwas zwischengespeichert.)

Tischgrößen:

  • Haupttabelle: 87 Zeilen,
  • erste Tabelle für Linksverknüpfung: 179 Zeilen;
  • zweite Tabelle für Linksverknüpfung: 7 Zeilen.

EF Core-Version: 2.2.1.

SQL Server-Version: MS SQL Server 2017 - 14 ... (unter Windows 10).

Alle relevanten Tabellen hatten nur Indizes für die Primärschlüssel.

Mein Fazit: Es wird immer empfohlen, sich das generierte SQL anzusehen, da es sich wirklich unterscheiden kann.


[1] Interessanterweise konnte ich beim Aktivieren der Clientstatistik in MS SQL Server Management Studio einen entgegengesetzten Trend feststellen. Der letzte Durchlauf der Lösung ohne Kreuzanwendung dauerte nämlich mehr als 1 s. Ich nehme an, dass hier etwas schief gelaufen ist - vielleicht mit meinem Setup.

Andreas Schütz
quelle