55

Próbuję LINQ do podmiotów.LEFT JOIN w LINQ do podmiotów?

Mam problem z następujących czynności: Chcę, żeby to zrobić:

SELECT 
    T_Benutzer.BE_User 
    ,T_Benutzer_Benutzergruppen.BEBG_BE 
FROM T_Benutzer 

LEFT JOIN T_Benutzer_Benutzergruppen 
    ON T_Benutzer_Benutzergruppen.BEBG_BE = T_Benutzer.BE_ID 

najbliższa rzecz Doszedłem do to:

 var lol = (
      from u in Repo.T_Benutzer 

      //where u.BE_ID == 1 
      from o in Repo.T_Benutzer_Benutzergruppen.DefaultIfEmpty() 
       // on u.BE_ID equals o.BEBG_BE 

      where (u.BE_ID == o.BEBG_BE || o.BEBG_BE == null) 

      //join bg in Repo.T_Benutzergruppen.DefaultIfEmpty() 
      // on o.BEBG_BG equals bg.ID 

      //where bg.ID == 899 

      orderby 
       u.BE_Name ascending 
       //, bg.Name descending 

      //select u 
      select new 
      { 
       u.BE_User 
       ,o.BEBG_BG 
       //, bg.Name 
      } 
     ).ToList(); 

Ale to generuje takie same wyniki jako łączenie wewnętrzne, a nie łączenie lewe.
Ponadto stwarza to całkowicie szalony SQL:

SELECT 
    [Extent1].[BE_ID] AS [BE_ID] 
    ,[Extent1].[BE_User] AS [BE_User] 
    ,[Join1].[BEBG_BG] AS [BEBG_BG] 
FROM [dbo].[T_Benutzer] AS [Extent1] 

CROSS JOIN 
(
    SELECT 
     [Extent2].[BEBG_BE] AS [BEBG_BE] 
     ,[Extent2].[BEBG_BG] AS [BEBG_BG] 
    FROM (SELECT 1 AS X) AS [SingleRowTable1] 
    LEFT OUTER JOIN [dbo].[T_Benutzer_Benutzergruppen] AS [Extent2] 
     ON 1 = 1 
) AS [Join1] 

WHERE [Extent1].[BE_ID] = [Join1].[BEBG_BE] 
OR [Join1].[BEBG_BE] IS NULL 

ORDER BY [Extent1].[BE_Name] ASC 

Jak mogę zrobić LEFT JOIN w LINQ-2-podmiotów w sposób gdzie inna osoba nadal może zrozumieć, co się robi w tym kodzie?

i najbardziej korzystnie w której generowane SQL wygląda następująco:

SELECT 
    T_Benutzer.BE_User 
    ,T_Benutzer_Benutzergruppen.BEBG_BE 
FROM T_Benutzer 

LEFT JOIN T_Benutzer_Benutzergruppen 
    ON T_Benutzer_Benutzergruppen.BEBG_BE = T_Benutzer.BE_ID 
+0

Duplikat - http://stackoverflow.com/questions/3404975/left-outer-join-in-linq – Anand

+1

@Anand: Nie, join zawsze powoduje wewnętrzne sprzężenie, a od bez warunku jest cross-join, wybraną odpowiedzią jest, pomimo wielu przestawień, błędna i niewystarczająca. –

Odpowiedz

103

Ah, mam to sam.
Dziwactwa i kwarki jednostek LINQ-2.
To wygląda najbardziej zrozumiałe:

var query2 = (
    from users in Repo.T_Benutzer 
    from mappings in Repo.T_Benutzer_Benutzergruppen 
     .Where(mapping => mapping.BEBG_BE == users.BE_ID).DefaultIfEmpty() 
    from groups in Repo.T_Benutzergruppen 
     .Where(gruppe => gruppe.ID == mappings.BEBG_BG).DefaultIfEmpty() 
    //where users.BE_Name.Contains(keyword) 
    // //|| mappings.BEBG_BE.Equals(666) 
    //|| mappings.BEBG_BE == 666 
    //|| groups.Name.Contains(keyword) 

    select new 
    { 
     UserId = users.BE_ID 
     ,UserName = users.BE_User 
     ,UserGroupId = mappings.BEBG_BG 
     ,GroupName = groups.Name 
    } 

); 


var xy = (query2).ToList(); 

Zdjąć .DefaultIfEmpty(), a dostaniesz sprzężenie wewnętrzne.
Tego właśnie szukałem.

+5

Tyle lat robiących dołącza w drugą stronę! Dzięki! – Todd

+1

Świetnie! Pierwszy raz widząc takie rzeczy. Dzięki – teapeng

34

można przeczytać artykuł napisałem dla łączy w LINQ here

var query = 
from u in Repo.T_Benutzer 
join bg in Repo.T_Benutzer_Benutzergruppen 
    on u.BE_ID equals bg.BEBG_BE 
into temp 
from j in temp.DefaultIfEmpty() 
select new 
{ 
    BE_User = u.BE_User, 
    BEBG_BG = (int?)j.BEBG_BG// == null ? -1 : j.BEBG_BG 
      //, bg.Name 
} 

Poniżej jest równoważne z zastosowaniem metod przedłużania :

var query = 
Repo.T_Benutzer 
.GroupJoin 
(
    Repo.T_Benutzer_Benutzergruppen, 
    x=>x.BE_ID, 
    x=>x.BEBG_BE, 
    (o,i)=>new {o,i} 
) 
.SelectMany 
(
    x => x.i.DefaultIfEmpty(), 
    (o,i) => new 
    { 
     BE_User = o.o.BE_User, 
     BEBG_BG = (int?)i.BEBG_BG 
    } 
); 
+0

To T_Benutzer_Benutzergruppen, a nie T_Benutzergruppen, ale poza tym prawidłowe. Zastanawiam się, jak to powinno działać, gdy lewe połączenie więcej niż dwóch tabel. Szukałem bardziej intuicyjnie zrozumiałej metody. I w końcu to znalazłem :) –

+0

Osobiście jestem przyzwyczajony do metod rozszerzenia i bardzo mi się podoba. Jeśli ciągle powtarzasz pary "GroupJoin" i "SelectMany' możesz mieć ładne, choć długie, rozwiązanie :) –

1

można wykorzystywać nie tylko w jednostkach, ale również procedura sklep lub inne źródło danych:

var customer = (from cus in _billingCommonservice.BillingUnit.CustomerRepository.GetAll() 
          join man in _billingCommonservice.BillingUnit.FunctionRepository.ManagersCustomerValue() 
          on cus.CustomerID equals man.CustomerID 
          // start left join 
          into a 
          from b in a.DefaultIfEmpty(new DJBL_uspGetAllManagerCustomer_Result()) 
          select new { cus.MobileNo1,b.ActiveStatus }); 
+0

Wystąpił błąd podczas kpiny z kwerendy z DefaultIfEmpty. Stąd pomysł stworzenia domyślnej klasy "a.DefaultIfEmpty (nowy DJBL_uspGetAllManagerCustomer_Result()) "i zadziałało! – Riga

+0

Niestety tworzenie obiektu powoduje, że test integracji kończy się niepowodzeniem, więc nie jest dobrym rozwiązaniem. – Riga

5

Może przyjdę później odpowiedzieć, ale teraz jestem w obliczu z tym ... jeśli nie pomaga to jeszcze jedno rozwiązanie (sposób, w jaki to rozwiązałem).

var query2 = (
    from users in Repo.T_Benutzer 
    join mappings in Repo.T_Benutzer_Benutzergruppen on mappings.BEBG_BE equals users.BE_ID into tmpMapp 
    join groups in Repo.T_Benutzergruppen on groups.ID equals mappings.BEBG_BG into tmpGroups 
    from mappings in tmpMapp.DefaultIfEmpty() 
    from groups in tmpGroups.DefaultIfEmpty() 
    select new 
    { 
     UserId = users.BE_ID 
     ,UserName = users.BE_User 
     ,UserGroupId = mappings.BEBG_BG 
     ,GroupName = groups.Name 
    } 

); 

Przy okazji, próbowałem używać kodu Stefana Steigera, który również pomaga, ale był wolniejszy jak diabli.

+1

Czy robisz to w Linq-2-Objects? Ponieważ będzie to powolne, ponieważ nie używa indeksu. –