LEFT JOIN in LINQ zu Entitäten?

68

Ich probiere LINQ für Entitäten aus.

Ich habe ein Problem mit Folgendem: Ich möchte, dass es dies tut:

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 

Das nächste, zu dem ich gekommen bin, ist Folgendes:

        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();

Dies führt jedoch zu denselben Ergebnissen wie eine innere Verknüpfung und nicht zu einer linken Verknüpfung.
Darüber hinaus erstellt es diese völlig verrückte 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

Wie kann ich eine Linksverknüpfung in LINQ-2-Entitäten so durchführen, dass eine andere Person noch verstehen kann, was in diesem Code getan wird?

und am meisten bevorzugt, wo das generierte SQL aussieht:

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 
Stefan Steiger
quelle
1
@Anand: Nein, Join ergibt immer einen inneren Join, und ohne Bedingung ist ein Cross-Join. Die ausgewählte Antwort ist trotz ihrer vielen positiven Stimmen falsch und unzureichend.
Stefan Steiger
1
In Verbindung stehender Beitrag - Linq beitreten iquery, wie man defaultifempty verwendet
RBT
Mögliches Duplikat von LEFT OUTER JOIN in LINQ
Bartho Bernsmann

Antworten:

123

Ah, ich habe es selbst.
Die Macken und Quarks von LINQ-2-Entitäten.
Das sieht am verständlichsten aus:

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();

Entfernen Sie die .DefaultIfEmpty(), und Sie erhalten eine innere Verbindung.
Das war es, wonach ich gesucht habe.

Stefan Steiger
quelle
1
Großartig! Zum ersten Mal so etwas zu sehen. Vielen Dank
TPG
41

Hier können Sie einen Artikel lesen, den ich für Joins in LINQ geschrieben habe

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 
}

Das Folgende entspricht den Erweiterungsmethoden:

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
    }
);
Giannis Paraskevopoulos
quelle
Es ist T_Benutzer_Benutzergruppen, nicht T_Benutzergruppen, aber sonst richtig. Fragen Sie sich nur, wie dies funktionieren soll, wenn Sie mehr als zwei Tabellen verbinden. Ich suchte nach einer intuitiv verständlicheren Methode. Und ich habe es endlich gefunden :)
Stefan Steiger
Persönlich bin ich an die Erweiterungsmethoden gewöhnt und ich mag sie wirklich sehr. Wenn Sie das GroupJoinund SelectManyPaare immer wieder wiederholen , können Sie eine schöne, wenn auch lange Lösung haben :)
Giannis Paraskevopoulos
5

Vielleicht komme ich später, um zu antworten, aber jetzt stehe ich vor dieser Frage ... Wenn es hilft, gibt es noch eine Lösung (so wie ich sie gelöst habe).

    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
    }

);

Übrigens habe ich versucht, den Stefan Steiger-Code zu verwenden, der auch hilft, aber er war höllisch langsamer.

Alejandro Gori
quelle
1
Tun Sie das zufällig in Linq-2-Objects? Weil es dort langsam sein wird, weil es keinen Index verwendet.
Stefan Steiger
5

Der einfache Weg ist die Verwendung des Schlüsselworts Let. Das funktioniert bei mir.

from AItem in Db.A
Let BItem = Db.B.Where(x => x.id == AItem.id ).FirstOrDefault() 
Where SomeCondition
Select new YourViewModel
{
    X1 = AItem.a,
    X2 = AItem.b,
    X3 = BItem.c
}

Dies ist eine Simulation von Left Join. Wenn nicht jedes Element in der Tabelle B mit dem Element A übereinstimmt, gibt BItem null zurück

Mahdi Moghimi
quelle
1
Dies ist genau das, was ich für meine 5-Tabellen-Abfrage brauchte !!! Vielen Dank für das Posten, es hat mir Stunden Kopfschmerzen erspart! :)
Jamie
1

Sie können dies nicht nur in Entitäten verwenden, sondern auch Prozeduren oder andere Datenquellen speichern:

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 });  
Atik Sarker
quelle
Beim Verspotten der Abfrage mit DefaultIfEmpty ist ein Fehler aufgetreten. Ich habe von hier aus die Idee, eine Standardklasse 'a.DefaultIfEmpty (neues DJBL_uspGetAllManagerCustomer_Result ())' zu erstellen, und es hat funktioniert!
Riga
Leider führt das Erstellen der Entität dazu, dass der Integrationstest fehlschlägt, was keine gute Lösung darstellt.
Riga