LINQ to SQL lewe połączenie zewnętrzne

140

Czy to zapytanie jest równoważne LEFT OUTERzłączeniu?

//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
źródło

Odpowiedzi:

167

Niezupełnie - ponieważ każdy „lewy” wiersz w lewym-zewnętrznym-złączeniu będzie pasował do 0-n „prawych” wierszy (w drugiej tabeli), gdzie -jak twój pasuje tylko do 0-1. Aby wykonać lewe sprzężenie zewnętrzne, potrzebujesz SelectManyi DefaultIfEmpty, na przykład:

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

( lub za pomocą metod rozszerzających )

Marc Gravell
źródło
19
Czy ktoś może wyjaśnić, jak działa ta szalona składnia? Nie widzę, jak którekolwiek z tych słów kluczowych w magiczny sposób sprawia, że ​​jest to lewe połączenie. Co robi "into sr"? Linq czasami mnie frustruje :)
Joe Phillips
2
@JoePhillips Mam duże doświadczenie w SQL, ale próba nauczenia się LINQ jest jak brodzenie w błocie. Zgadzam się, że to absolutnie szalone.
Nick.McDermaid
@ marc-gravell: Czy możesz mi pomóc w rozwiązaniu mojego zapytania sql do konwersji linq: stackoverflow.com/questions/28367941/…
Vishal I Patil
@VishalIPatil dlaczego chcesz konwertować z SQL do LINQ? SQL działa dobrze i jest dużo bardziej przewidywalny i wydajny ...
Marc Gravell
1
@VishalIPatil więc ... dlaczego to robisz? Prawie każde narzędzie LINQ zawiera możliwość uruchamiania odręcznego języka SQL. Dlaczego po prostu tego nie zrobić?
Marc Gravell
216

Nie potrzebujesz instrukcji 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

I tak, powyższe zapytanie rzeczywiście tworzy lewe sprzężenie zewnętrzne.

Link do podobnego pytania, które obsługuje wiele złączeń po lewej stronie: Linq do Sql: Wiele złączeń zewnętrznych po lewej stronie

Amir
źródło
14
Chociaż wiem, że odpowiedź @Marc Gravvel działa, naprawdę wolę tę metodę, ponieważ IMO wydaje się być bardziej zgodna z tym, jak powinno wyglądać lewe złącze.
llaughlin
1
Doskonała odpowiedź. Szukam więcej niż 5 godzin wyszukiwania w Google. Jest to jedyny sposób, w jaki wynikowy kod SQL pozostawi do niego dołączenie.
Faisal Mq
1
Bardzo dziękuję… Szukałem rozwiązania na to całe popołudnie, a Twój kod go załatwił (i wydaje się naturalny do uruchomienia). Chciałbym móc głosować za tym kilka razy.
Jim
2
@Jim dzięki :-) Cieszę się, że deweloperzy wciąż nie mają czasu na tę odpowiedź. Całkowicie się zgadzam, że DefaultIfEmpty () wydaje się o wiele bardziej naturalne niż użycie instrukcji into.
Amir
7
Tylko uwaga dla każdego, kto znajdzie to tak, jak właśnie ja, skutkuje to LEFT OUTER JOIN wewnątrz CROSS APPLY, co oznacza, że ​​otrzymasz duplikaty, jeśli jest wiele dopasowań po prawej stronie połączenia. Rozwiązanie Marca Gravella, chociaż nie było tak „ładne”, dało mi odpowiednie wyjście SQL i zestaw wyników, których szukałem.
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

Sprawdź http://msdn.microsoft.com/en-us/vbasic/bb737929.aspx

Krishnaraj Barvathaya
źródło
Niezła próba, ale wygląda na to, że OP używa języka C #. Składnia VB jest dziwnie inna.
Levitikon,
5

Znalazłem 1 rozwiązanie. jeśli chcesz przetłumaczyć ten rodzaj SQL (lewe sprzężenie) na 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...
}
mokth
źródło
Zobacz ten komentarz , jednostki Linq-to-SQL nie obsługują DefaultIfEmpty.
TJ Crowder,
2

Chciałbym dodać jeszcze jedną rzecz. W LINQ to SQL, jeśli twoja baza danych jest poprawnie zbudowana, a tabele są powiązane za pomocą ograniczeń klucza obcego, nie musisz w ogóle wykonywać sprzężenia.

Za pomocą LINQPad utworzyłem następujące zapytanie LINQ:

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

Który został przetłumaczony na (lekko obcięte) zapytanie poniżej

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

Zwróć uwagę na LEFT OUTER JOINpowyższe.

Brian Kraemer
źródło
1

Zadbaj o wydajność:

Doświadczyłem, że przynajmniej w przypadku EF Core różne odpowiedzi podane tutaj mogą skutkować inną wydajnością. Zdaję sobie sprawę, że OP pytał o Linq do SQL, ale wydaje mi się, że te same pytania pojawiają się również z EF Core.

W konkretnym przypadku, z którym miałem do czynienia, (ładniejsza składniowo) sugestia Marca Gravella spowodowała, że ​​lewe łączenia wewnątrz krzyża zostały zastosowane - podobnie do tego, co opisał Mike U - co skutkowało tym, że szacunkowe koszty tego konkretnego zapytania wyniosły dwa razy wyższy w porównaniu z zapytaniem bez połączeń krzyżowych . Czasy wykonania serwera różniły się 3- krotnie . [1]

Rozwiązanie autorstwa Marca Gravella spowodowało zapytanie bez łączeń krzyżowych.

Kontekst: Zasadniczo potrzebowałem wykonać dwa łączenia po lewej stronie na dwóch stołach, z których każdy ponownie wymagał połączenia z innym stołem. Ponadto musiałem określić inne warunki gdzie-w tabelach, na których musiałem zastosować lewe sprzężenie. Ponadto miałem dwa wewnętrzne łączenia na głównym stole.

Szacunkowe koszty operatora:

  • z krzyżem: 0,2534
  • bez krzyżyka: 0,0991.

Czasy wykonania serwera w ms (zapytania wykonywane 10 razy; mierzone za pomocą funkcji SET STATISTICS TIME ON):

  • z krzyżem stosuje się: 5, 6, 6, 6, 6, 6, 6, 6, 6, 6
  • bez krzyżyka stosuje się: 2, 2, 2, 2, 2, 2, 2, 2, 2, 2

(Pierwsze uruchomienie było wolniejsze dla obu zapytań; wydaje się, że coś jest w pamięci podręcznej).

Rozmiary stołów:

  • stół główny: 87 rzędów,
  • pierwsza tabela dla złączenia lewego: 179 wierszy;
  • druga tabela dla złączenia lewego: 7 rzędów.

Wersja EF Core: 2.2.1.

Wersja SQL Server: MS SQL Server 2017-14 ... (w systemie Windows 10).

Wszystkie odpowiednie tabele miały indeksy tylko dla kluczy podstawowych.

Mój wniosek: zawsze zaleca się przyjrzenie się wygenerowanemu kodowi SQL, ponieważ może się naprawdę różnić.


[1] Co ciekawe, ustawiając „Statystyki klienta” w MS SQL Server Management Studio na, zauważyłem odwrotny trend; mianowicie, że ostatnie uruchomienie rozwiązania bez zastosowania krzyżyka zajęło więcej niż 1 s. Przypuszczam, że coś tu było nie tak - może z moją konfiguracją.

Andreas Schütz
źródło