Ich habe eine Liste der Personenausweise und ihres Vornamens sowie eine Liste der Personenausweise und ihres Nachnamens. Einige Leute haben keinen Vornamen und andere keinen Nachnamen. Ich möchte einen vollständigen äußeren Join für die beiden Listen durchführen.
Also die folgenden Listen:
ID FirstName
-- ---------
1 John
2 Sue
ID LastName
-- --------
1 Doe
3 Smith
Sollte produzieren:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
3 Smith
Ich bin neu bei LINQ (also vergib mir, wenn ich lahm bin) und habe einige Lösungen für 'LINQ Outer Joins' gefunden, die alle ziemlich ähnlich aussehen, aber wirklich äußere Joins zu sein scheinen.
Meine bisherigen Versuche gehen ungefähr so:
private void OuterJoinTest()
{
List<FirstName> firstNames = new List<FirstName>();
firstNames.Add(new FirstName { ID = 1, Name = "John" });
firstNames.Add(new FirstName { ID = 2, Name = "Sue" });
List<LastName> lastNames = new List<LastName>();
lastNames.Add(new LastName { ID = 1, Name = "Doe" });
lastNames.Add(new LastName { ID = 3, Name = "Smith" });
var outerJoin = from first in firstNames
join last in lastNames
on first.ID equals last.ID
into temp
from last in temp.DefaultIfEmpty()
select new
{
id = first != null ? first.ID : last.ID,
firstname = first != null ? first.Name : string.Empty,
surname = last != null ? last.Name : string.Empty
};
}
}
public class FirstName
{
public int ID;
public string Name;
}
public class LastName
{
public int ID;
public string Name;
}
Aber das kehrt zurück:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
Was mache ich falsch?
c#
.net
linq
outer-join
full-outer-join
ninjaPixel
quelle
quelle
Antworten:
Ich weiß nicht, ob dies alle Fälle abdeckt, logischerweise scheint es richtig zu sein. Die Idee ist, eine linke äußere Verbindung und eine rechte äußere Verbindung zu nehmen und dann die Vereinigung der Ergebnisse vorzunehmen.
Dies funktioniert wie geschrieben, da es sich in LINQ to Objects befindet. Bei LINQ to SQL oder anderen unterstützt der Abfrageprozessor möglicherweise keine sichere Navigation oder andere Vorgänge. Sie müssten den bedingten Operator verwenden, um die Werte bedingt abzurufen.
dh
quelle
AsEnumerable()
bevor Sie die Vereinigung / Verkettung durchführen. Versuchen Sie das und sehen Sie, wie das geht. Wenn dies nicht der Weg ist, den Sie gehen möchten, bin ich mir nicht sicher, ob ich Ihnen weiterhelfen kann.Update 1: Bereitstellung einer wirklich verallgemeinerten Erweiterungsmethode
FullOuterJoin
Update 2: Optionales Akzeptieren einer benutzerdefinierten
IEqualityComparer
Methode für den SchlüsseltypUpdate 3 : Diese Implementierung wurde kürzlich Teil von
MoreLinq
- Danke Jungs!Bearbeiten hinzugefügt
FullOuterGroupJoin
( ideone ). Ich habe dieGetOuter<>
Implementierung wiederverwendet , wodurch sie um einen Bruchteil weniger leistungsfähig ist, als es sein könnte, aber ich strebe derzeit einen Code auf hoher Ebene an, der nicht auf dem neuesten Stand ist.Sehen Sie es live auf http://ideone.com/O36nWc
Druckt die Ausgabe:
Sie können auch Standardeinstellungen angeben: http://ideone.com/kG4kqO
Drucken:
Erklärung der verwendeten Begriffe:
Joining ist ein Begriff, der aus dem relationalen Datenbankdesign entlehnt wurde:
a
so oft, wie Elementeb
mit dem entsprechenden Schlüssel vorhanden sind (dh: nichts, wennb
leer). Datenbankjargon nennt diesinner (equi)join
.a
für die kein entsprechendes Element vorhanden istb
. (dh: sogar Ergebnisse, wennb
leer). Dies wird üblicherweise als bezeichnetleft join
.a
sowie,b
wenn im anderen kein entsprechendes Element vorhanden ist. (dh sogar Ergebnisse, wenna
leer waren)Etwas, das in RDBMS normalerweise nicht zu sehen ist, ist ein Gruppenbeitritt [1] :
a
für mehrere entsprechende zu wiederholenb
, gruppiert er die Datensätze mit entsprechenden Schlüsseln. Dies ist häufig praktischer, wenn Sie anhand eines gemeinsamen Schlüssels durch "verbundene" Datensätze auflisten möchten.Siehe auch GroupJoin, das auch einige allgemeine Hintergrunderklärungen enthält.
[1] (Ich glaube, Oracle und MSSQL haben dafür proprietäre Erweiterungen.)
Vollständiger Code
Eine verallgemeinerte 'Drop-In'-Erweiterungsklasse dafür
quelle
FullOuterJoin
bereitgestellten Erweiterungsmethode zu zeigena.GroupBy(selectKeyA).ToDictionary();
alsa.ToLookup(selectKeyA)
undadict.OuterGet(key)
als schreibenalookup[key]
. Das Abrufen der Schlüsselsammlung ist jedoch etwas schwieriger :alookup.Select(x => x.Keys)
.Ich denke, es gibt Probleme mit den meisten dieser Probleme, einschließlich der akzeptierten Antwort, da sie mit Linq über IQueryable nicht gut funktionieren, entweder weil zu viele Server-Roundtrips und zu viele Daten zurückgegeben werden oder weil zu viele Clients ausgeführt werden.
Für IEnumerable mag ich Sehes Antwort oder ähnliches nicht, weil sie übermäßig viel Speicher benötigt (ein einfacher 10000000-Test mit zwei Listen führte dazu, dass Linqpad auf meinem 32-GB-Computer nicht mehr über genügend Speicher verfügte).
Außerdem implementieren die meisten anderen keine ordnungsgemäße vollständige äußere Verknüpfung, da sie eine Union mit einer rechten Verknüpfung anstelle von Concat mit einer rechten Antisemi-Verknüpfung verwenden, wodurch nicht nur die doppelten inneren Verknüpfungszeilen aus dem Ergebnis entfernt werden, sondern auch Alle richtigen Duplikate, die ursprünglich in den linken oder rechten Daten vorhanden waren.
Hier sind meine Erweiterungen, die all diese Probleme behandeln, SQL generieren sowie den Join in LINQ zu SQL direkt implementieren, auf dem Server ausgeführt werden und schneller und mit weniger Speicher als andere in Enumerables ausgeführt werden:
Der Unterschied zwischen einem rechten Antisemi-Join ist meistens bei Linq to Objects oder in der Quelle umstritten, macht jedoch in der endgültigen Antwort einen Unterschied auf der Serverseite (SQL), wodurch unnötige entfernt werden
JOIN
.Die Handcodierung für
Expression
das Zusammenführen einesExpression<Func<>>
zu einem Lambda könnte mit LinqKit verbessert werden, aber es wäre schön, wenn die Sprache / der Compiler eine Hilfe dafür hinzugefügt hätte. DieFullOuterJoinDistinct
undRightOuterJoin
Funktionen sind der Vollständigkeit halber enthalten, aber ich habe sie noch nicht neu implementiertFullOuterGroupJoin
.Ich habe eine andere Version eines vollständigen äußeren Joins für
IEnumerable
Fälle geschrieben, in denen der Schlüssel bestellbar ist. Dies ist etwa 50% schneller als das Kombinieren des linken äußeren Joins mit dem rechten Anti-Semi-Join, zumindest bei kleinen Sammlungen. Es geht durch jede Sammlung nach nur einmaligem Sortieren.Ich habe auch eine weitere Antwort für eine Version hinzugefügt , die mit EF funktioniert, indem ich die
Invoke
durch eine benutzerdefinierte Erweiterung ersetzt habe.quelle
TP unusedP, TC unusedC
los? Sind sie buchstäblich unbenutzt?TP
,TC
, umTResult
die richtigen zu erstellenExpression<Func<>>
. Ich sollte ich sie mit ersetzen könnte_
,__
,___
statt, aber das scheint nicht klarer bis C # einen richtigen Parameter Wildcard hat stattdessen zu verwenden.The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
. Gibt es irgendwelche Einschränkungen mit diesem Code? Ich möchte einen vollständigen Beitritt über IQueryables durchführenInvoke
mit einem individuell gestaltetenExpressionVisitor
die InlineInvoke
so dass es mit EF funktionieren sollte. Kannst du es versuchen?Hier ist eine Erweiterungsmethode, die dies tut:
quelle
Union
entfernt Duplikate. Wenn die Originaldaten also doppelte Zeilen enthalten, werden diese nicht im Ergebnis angezeigt .Ich vermute, dass der Ansatz von @ sehe stärker ist, aber bis ich es besser verstehe, bin ich von der Erweiterung von @ MichaelSander abgesprungen. Ich habe es geändert, um es an die Syntax und den Rückgabetyp der hier beschriebenen integrierten Enumerable.Join () -Methode anzupassen . Ich habe das "eindeutige" Suffix in Bezug auf den Kommentar von @ cadrell0 unter der Lösung von @ JeffMercado angehängt.
Im Beispiel würden Sie es folgendermaßen verwenden:
Wenn ich in Zukunft mehr lerne, habe ich das Gefühl, dass ich angesichts der Popularität auf die Logik von @ sehe migrieren werde. Aber selbst dann muss ich vorsichtig sein, da ich es aus zwei Gründen für wichtig halte, mindestens eine Überladung zu haben, die der Syntax der vorhandenen ".Join ()" - Methode entspricht, wenn dies machbar ist:
Ich bin noch neu mit Generika, Erweiterungen, Func-Anweisungen und anderen Funktionen, daher ist Feedback auf jeden Fall willkommen.
EDIT: Ich habe nicht lange gebraucht, um festzustellen, dass ein Problem mit meinem Code vorliegt. Ich habe einen .Dump () in LINQPad ausgeführt und mir den Rückgabetyp angesehen. Es war nur IEnumerable, also habe ich versucht, es abzugleichen. Aber als ich tatsächlich ein .Where () oder .Select () für meine Erweiterung ausgeführt habe, wurde eine Fehlermeldung angezeigt: "'System Collections.IEnumerable' enthält keine Definition für 'Select' und ...". Am Ende konnte ich also die Eingabesyntax von .Join () anpassen, aber nicht das Rückgabeverhalten.
EDIT: Added „TResult“ der Rückgabetyp für die Funktion. Ich habe das beim Lesen des Microsoft-Artikels verpasst und es macht natürlich Sinn. Mit diesem Fix scheint das Rückkehrverhalten nun doch meinen Zielen zu entsprechen.
quelle
Wie Sie festgestellt haben, verfügt Linq nicht über ein Konstrukt "Outer Join". Der nächste, den Sie erhalten können, ist ein linker äußerer Join mit der von Ihnen angegebenen Abfrage. Dazu können Sie alle Elemente der Nachnamenliste hinzufügen, die nicht im Join enthalten sind:
quelle
Ich mag die Antwort von sehe, aber es wird keine verzögerte Ausführung verwendet (die Eingabesequenzen werden durch die Aufrufe von ToLookup eifrig aufgezählt). Nachdem ich mir die .NET-Quellen für LINQ-to-Objects angesehen hatte , kam ich zu folgendem Ergebnis :
Diese Implementierung hat die folgenden wichtigen Eigenschaften:
Diese Eigenschaften sind wichtig, da sie das sind, was jemand erwartet, der neu bei FullOuterJoin ist, aber Erfahrung mit LINQ hat.
quelle
Ich habe beschlossen, dies als separate Antwort hinzuzufügen, da ich nicht sicher bin, dass es ausreichend getestet wurde. Dies ist eine Neuimplementierung der
FullOuterJoin
Methode unter Verwendung einer vereinfachten, angepassten Version vonLINQKit
Invoke
/Expand
for,Expression
damit sie mit dem Entity Framework funktioniert. Es gibt nicht viel Erklärung, da es so ziemlich das gleiche ist wie meine vorherige Antwort.quelle
base.Visit(node)
sollte keine Ausnahme auslösen, da dies nur den Baum hinunter rekursiert. Ich kann auf so ziemlich jeden Code-Sharing-Dienst zugreifen, aber keine Testdatenbank einrichten. Das Ausführen mit meinem LINQ to SQL-Test scheint jedoch gut zu funktionieren.Guid
Schlüssel und einemGuid?
Fremdschlüssel verbinden?Führt eine speicherinterne Streaming-Aufzählung über beide Eingänge durch und ruft den Selektor für jede Zeile auf. Wenn bei der aktuellen Iteration keine Korrelation besteht, ist eines der Auswahlargumente null .
Beispiel:
Benötigt einen IComparer für den Korrelationstyp, verwendet den Comparer.Default, falls nicht angegeben.
Erfordert, dass 'OrderBy' auf die Eingabeaufzählungen angewendet wird
quelle
OrderBy
beide Schlüsselprojektionen ausführen .OrderBy
puffert die gesamte Sequenz aus den offensichtlichen Gründen .Meine saubere Lösung für die Situation, dass der Schlüssel in beiden Aufzählungen eindeutig ist:
so
Ausgänge:
quelle
Vollständiger äußerer Join für zwei oder mehr Tabellen: Extrahieren Sie zuerst die Spalte, für die Sie einen Join erstellen möchten.
Verwenden Sie dann die linke äußere Verknüpfung zwischen der extrahierten Spalte und den Haupttabellen.
quelle
Ich habe diese Erweiterungsklasse vor vielleicht 6 Jahren für eine App geschrieben und verwende sie seitdem in vielen Lösungen ohne Probleme. Ich hoffe es hilft.
Bearbeiten: Ich habe festgestellt, dass einige möglicherweise nicht wissen, wie eine Erweiterungsklasse verwendet wird.
Um diese Erweiterungsklasse zu verwenden, verweisen Sie einfach auf ihren Namespace in Ihrer Klasse, indem Sie die folgende Zeile mit joinext hinzufügen.
^ Dies sollte es Ihnen ermöglichen, die Intelligenz der Erweiterungsfunktionen in jeder IEnumerable-Objektsammlung zu sehen, die Sie gerade verwenden.
Hoffe das hilft. Lassen Sie mich wissen, wenn es immer noch nicht klar ist, und ich werde hoffentlich ein Beispielbeispiel für die Verwendung schreiben.
Hier ist die Klasse:
quelle
SelectMany
nicht in einen LINQ2SQL-würdigen Ausdrucksbaum konvertiert werden zu können.Ich denke, dass die LINQ-Join-Klausel nicht die richtige Lösung für dieses Problem ist, da der Zweck der Join-Klausel nicht darin besteht, Daten so zu akkumulieren, wie es für diese Task-Lösung erforderlich ist. Der Code zum Zusammenführen erstellter separater Sammlungen wird zu kompliziert. Vielleicht ist er für Lernzwecke in Ordnung, aber nicht für echte Anwendungen. Eine Möglichkeit, dieses Problem zu lösen, finden Sie im folgenden Code:
Wenn echte Sammlungen für die HashSet-Bildung groß sind, können stattdessen für jede Schleife der folgende Code verwendet werden:
quelle
Vielen Dank an alle für die interessanten Beiträge!
Ich habe den Code geändert, weil ich ihn in meinem Fall brauchte
Für diejenigen, die interessiert sind, ist dies mein geänderter Code (in VB, sorry)
quelle
Noch eine vollständige äußere Verbindung
Da ich mit der Einfachheit und Lesbarkeit der anderen Sätze nicht so zufrieden war, kam ich zu folgendem Ergebnis:
Es hat nicht den Anspruch, schnell zu sein (ungefähr 800 ms, um 1000 * 1000 auf einer 2020m-CPU zu verbinden: 2,4 GHz / 2 Kerne). Für mich ist es nur eine kompakte und lässige vollständige äußere Verbindung.
Es funktioniert genauso wie ein SQL FULL OUTER JOIN (doppelte Speicherung)
Prost ;-)
Die Idee ist zu
Hier ist ein prägnanter Test, der dazu gehört:
Platzieren Sie am Ende einen Haltepunkt, um manuell zu überprüfen, ob er sich wie erwartet verhält
}}
quelle
Ich hasse diese linq-Ausdrücke wirklich, deshalb gibt es SQL:
Erstellen Sie diese als SQL-Ansicht in der Datenbank und importieren Sie sie als Entität.
Natürlich wird es auch eine (eindeutige) Vereinigung von linken und rechten Verbindungen schaffen, aber es ist dumm.
quelle