664 Stimmen

LEFT OUTER JOIN in LINQ

Wie führt man eine linke äußere Verknüpfung in C# LINQ to objects ohne Verwendung von join-on-equals-into Klauseln? Gibt es eine Möglichkeit, dies zu tun mit where Klausel? Richtiges Problem: Für inner join ist einfach und ich habe eine Lösung wie diese

List<JoinPair> innerFinal = (from l in lefts from r in rights where l.Key == r.Key
                             select new JoinPair { LeftId = l.Id, RightId = r.Id})

aber für die linke äußere Verbindung brauche ich eine Lösung. Meine ist etwas wie dieses, aber es funktioniert nicht

List< JoinPair> leftFinal = (from l in lefts from r in rights
                             select new JoinPair { 
                                            LeftId = l.Id, 
                                            RightId = ((l.Key==r.Key) ? r.Id : 0
                                        })

donde JoinPair ist eine Klasse:

public class JoinPair { long leftId; long rightId; }

3 Stimmen

Können Sie ein Beispiel dafür geben, was Sie zu erreichen versuchen?

0 Stimmen

Normale linke äußere Verknüpfung ist so etwas wie dieses: var a = from b in bb join c in cc on b.bbbbb equals c.ccccc into dd from d in dd.DefaultIfEmpty() select b.sss; Meine Frage ist es eine Möglichkeit zu tun, dass ohne Verwendung von join-on-equals-into-Klauseln etwas wie dieses var a = from b in bb from c in cc where b.bbb == c.cccc ... und so weiter...

2 Stimmen

Sicher gibt es, aber Sie sollten ein Beispiel für Ihren Code posten, den Sie bereits haben, damit die Leute Ihnen eine bessere Antwort geben können

752voto

ajay_whiz Punkte 16775

Wie in "Linke äußere Verknüpfungen durchführen" :

var q =
    from c in categories
    join p in products on c.Category equals p.Category into ps
    from p in ps.DefaultIfEmpty()
    select new { Category = c, ProductName = p == null ? "(No products)" : p.ProductName };

7 Stimmen

Ich versuche dasselbe, erhalte aber eine Fehlermeldung für den Verknüpfungsoperator, die besagt: "Der Typ eines der Ausdrücke in der Verknüpfungsklausel ist falsch".

3 Stimmen

@jain wenn Ihre Typen unterschiedlich sind, wird die Verbindung nicht funktionieren. Wahrscheinlich haben Ihre Schlüssel also unterschiedliche Datentypen. Sind beide Schlüssel zum Beispiel int?

0 Stimmen

Ja, das war es, übrigens habe ich die Lösung. Vielen Dank für Ihre Zeit.

644voto

Stefan Steiger Punkte 72861

Wenn ein datenbankgestützter LINQ-Anbieter verwendet wird, kann eine wesentlich lesbarere linke äußere Verknüpfung als solche geschrieben werden:

from maintable in Repo.T_Whatever 
from xxx in Repo.T_ANY_TABLE.Where(join condition).DefaultIfEmpty()

Wenn Sie die DefaultIfEmpty() haben Sie eine innere Verbindung.

Nehmen Sie die akzeptierte Antwort:

  from c in categories
    join p in products on c equals p.Category into ps
    from p in ps.DefaultIfEmpty()

Diese Syntax ist sehr verwirrend, und es ist nicht klar, wie sie funktioniert, wenn man MEHRERE Tabellen links verknüpfen will.

Hinweis
Es ist zu beachten, dass from alias in Repo.whatever.Where(condition).DefaultIfEmpty() ist dasselbe wie ein outer-apply/left-join-lateral, das jeder (anständige) Datenbank-Optimierer perfekt in einen left-join übersetzen kann, solange Sie keine per-row-Werte einführen (aka ein tatsächliches outer apply). Tun Sie dies nicht in Linq-2-Objects (denn es gibt keine DB-Optimierer, wenn Sie Linq-to-Objects verwenden).

Ausführliches Beispiel

var query2 = (
    from users in Repo.T_User
    from mappings in Repo.T_User_Group
         .Where(mapping => mapping.USRGRP_USR == users.USR_ID)
         .DefaultIfEmpty() // <== makes join left join
    from groups in Repo.T_Group
         .Where(gruppe => gruppe.GRP_ID == mappings.USRGRP_GRP)
         .DefaultIfEmpty() // <== makes join left join

    // where users.USR_Name.Contains(keyword)
    // || mappings.USRGRP_USR.Equals(666)  
    // || mappings.USRGRP_USR == 666 
    // || groups.Name.Contains(keyword)

    select new
    {
         UserId = users.USR_ID
        ,UserName = users.USR_User
        ,UserGroupId = groups.ID
        ,GroupName = groups.Name
    }

);

var xy = (query2).ToList();

Bei Verwendung von LINQ 2 SQL lässt sich die folgende sehr gut lesbare SQL-Abfrage erstellen:

SELECT 
     users.USR_ID AS UserId 
    ,users.USR_User AS UserName 
    ,groups.ID AS UserGroupId 
    ,groups.Name AS GroupName 
FROM T_User AS users

LEFT JOIN T_User_Group AS mappings
   ON mappings.USRGRP_USR = users.USR_ID

LEFT JOIN T_Group AS groups
    ON groups.GRP_ID == mappings.USRGRP_GRP

Editar:

Siehe auch " SQL Server-Abfrage in Linq-Abfrage konvertieren " für ein komplexeres Beispiel.

Auch, wenn Sie es in Linq-2-Objekte (anstelle von Linq-2-SQL) tun, sollten Sie es die altmodische Art und Weise tun (weil LINQ to SQL dies korrekt auf Join-Operationen übersetzt, aber über Objekte erzwingt diese Methode einen vollständigen Scan, und nutzt nicht die Vorteile der Indexsuche, warum auch immer...):

    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
    }

);

28 Stimmen

Diese Antwort ist wirklich hilfreich. Danke, dass Sie tatsächlich eine verständliche Syntax anbieten.

3 Stimmen

WTB eine NHibernate kompatible LINQ-Abfrage... :)

35 Stimmen

LINQ to SQL setzt dies korrekt in Join-Operationen um. Bei Objekten erzwingt diese Methode jedoch einen vollständigen Scan. Aus diesem Grund bietet die offizielle Dokumentation die Lösung der Gruppenverknüpfung an, die die Vorteile von Hashes für die Indexsuche nutzen kann.

188voto

N Rocking Punkte 2657

Lambda-Ausdruck verwenden

db.Categories    
  .GroupJoin(db.Products,
      Category => Category.CategoryId,
      Product => Product.CategoryId,
      (x, y) => new { Category = x, Products = y })
  .SelectMany(
      xy => xy.Products.DefaultIfEmpty(),
      (x, y) => new { Category = x.Category, Product = y })
  .Select(s => new
  {
      CategoryName = s.Category.Name,     
      ProductName = s.Product.Name   
  });

0 Stimmen

Müssen Sie die .GroupJoin oder können Sie auch einfach .Join ?

12 Stimmen

Sowohl Join als auch GroupJoin unterstützen nicht wirklich den Links-Join. Der Trick bei der Verwendung von GroupJoin ist, dass Sie leere Gruppen haben können und diese leeren Gruppen dann in leere Werte übersetzen können. DefaultIfEmpty macht genau das, was bedeutet Enumerable.Empty<Product>.DefaultIfEmpty() gibt eine IEnumerable mit einem einzigen Wert von default(Product) .

106 Stimmen

All dies, um eine linke Verbindung herzustellen?

61voto

Matas Vaitkevicius Punkte 53532

Jetzt als Erweiterungsmethode:

public static class LinqExt
{
    public static IEnumerable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(this IEnumerable<TLeft> left, IEnumerable<TRight> right, Func<TLeft, TKey> leftKey, Func<TRight, TKey> rightKey,
        Func<TLeft, TRight, TResult> result)
    {
        return left.GroupJoin(right, leftKey, rightKey, (l, r) => new { l, r })
             .SelectMany(
                 o => o.r.DefaultIfEmpty(),
                 (l, r) => new { lft= l.l, rght = r })
             .Select(o => result.Invoke(o.lft, o.rght));
    }
}

Verwenden Sie es so, wie Sie normalerweise join verwenden würden:

var contents = list.LeftOuterJoin(list2, 
             l => l.country, 
             r => r.name,
            (l, r) => new { count = l.Count(), l.country, l.reason, r.people })

Ich hoffe, das spart Ihnen etwas Zeit.

2 Stimmen

Das ist schön, aber in Ihrem Beispiel wird nicht r.people eine Ausnahme auslösen, wenn list enthält Tasten list2 nicht, weil r wird null ? Müsste es nicht heißen r?.people ? Ansonsten ist es nur eine innere Verknüpfung, die auch Ausnahmen auslöst. Oder, ich denke, Sie könnten einen Parameter "default right element" hinzufügen zu LeftOuterJoin() und übergibt sie an DefaultIfEmpty() .

4 Stimmen

Sollte dies nicht für IQueryable stattdessen?

52voto

Devart Punkte 114874

Schauen Sie sich das an Beispiel . Diese Abfrage sollte funktionieren:

var leftFinal = from left in lefts
                join right in rights on left equals right.Left into leftRights
                from leftRight in leftRights.DefaultIfEmpty()
                select new { LeftId = left.Id, RightId = left.Key==leftRight.Key ? leftRight.Id : 0 };

3 Stimmen

Dose r in der select-Klausel nach der Verwendung eines join into zugegriffen werden?

0 Stimmen

@FarhadAlizadehNoori Ja, das kann es.

0 Stimmen

Der Autor wollte wahrscheinlich wiederverwenden r in der zweiten from Klausel. d.h. from r in lrs.DefaultIfEmpty() Andernfalls macht diese Abfrage nicht viel Sinn und lässt sich wahrscheinlich nicht einmal kompilieren, weil r für die Auswahl aus dem Zusammenhang gerissen werden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X