Mam listę osób i ich imię oraz listę osób i ich nazwiska. Niektóre osoby nie mają imienia, a niektóre nie mają nazwiska; Chciałbym wykonać pełne zewnętrzne połączenie na dwóch listach.
Więc następujące listy:
ID FirstName
-- ---------
1 John
2 Sue
ID LastName
-- --------
1 Doe
3 Smith
Powinien produkować:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
3 Smith
Jestem nowy w LINQ (więc wybacz mi, gdy jestem kulawy) i znalazłem sporo rozwiązań dla „Zewnętrznych połączeń LINQ”, które wyglądają dość podobnie, ale tak naprawdę wydają się być zewnętrznymi złączeniami.
Moje dotychczasowe próby idą mniej więcej tak:
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;
}
Ale to zwraca:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
Co ja robię źle?
c#
.net
linq
outer-join
full-outer-join
ninjaPixel
źródło
źródło
Odpowiedzi:
Nie wiem, czy dotyczy to wszystkich przypadków, logicznie wydaje się to poprawne. Chodzi o to, aby wziąć lewe łączenie zewnętrzne i prawe połączenie zewnętrzne, a następnie wziąć połączenie wyników.
Działa to tak, jak napisano, ponieważ znajduje się w LINQ to Objects. Jeśli LINQ to SQL lub inny, procesor zapytań może nie obsługiwać bezpiecznej nawigacji lub innych operacji. Musisz użyć operatora warunkowego, aby warunkowo uzyskać wartości.
to znaczy,
źródło
AsEnumerable()
przed wykonaniem unii / konkatenacji. Spróbuj i przekonaj się, jak to idzie. Jeśli to nie jest droga, którą chcesz się udać, nie jestem pewien, czy mogę ci w czymś pomóc.Aktualizacja 1: zapewnienie prawdziwie uogólnionej metody rozszerzenia
FullOuterJoin
Aktualizacja 2: opcjonalnie akceptacja niestandardowego
IEqualityComparer
typu kluczaAktualizacja 3 : ta implementacja stała się niedawno częścią
MoreLinq
- Dzięki, chłopaki!Edytuj dodane
FullOuterGroupJoin
( ideone ). Użyłem ponownieGetOuter<>
implementację, dzięki czemu ta część jest mniej wydajna, niż mogłaby być, ale obecnie dążę do kodu „wysokiego poziomu”, a nie zoptymalizowanego pod kątem najnowszych technologii.Zobacz na żywo na http://ideone.com/O36nWc
Drukuje dane wyjściowe:
Możesz także podać wartości domyślne: http://ideone.com/kG4kqO
Druk:
Wyjaśnienie użytych terminów:
Dołączanie jest terminem zapożyczonym z projektu relacyjnej bazy danych:
a
tyle razy, ile istnieją elementyb
z odpowiadającym kluczem (tzn nic jeślib
były puste). Lingo bazy danych nazywa toinner (equi)join
.a
dla których nie istnieje odpowiedni elementb
. (tzn .: nawet wyniki, jeślib
były puste). Jest to zwykle określane jakoleft join
.a
, jak równieżb
, jeżeli nie ma odpowiedni element istnieje w drugiej. (tzn. nawet wyniki, jeślia
były puste)Coś, czego zwykle nie widać w RDBMS, to dołączenie do grupy [1] :
a
o wielokrotność odpowiadab
, to grupy Rekordy z odpowiednich klawiszy. Jest to często wygodniejsze, gdy chcesz wyliczyć rekordy „połączone” na podstawie wspólnego klucza.Zobacz także GroupJoin, który zawiera również ogólne wyjaśnienia.
[1] (Wierzę, że Oracle i MSSQL mają do tego zastrzeżone rozszerzenia)
Pełny kod
Uogólniona klasa rozszerzenia „drop-in” do tego celu
źródło
FullOuterJoin
podanej metody rozszerzeniaa.GroupBy(selectKeyA).ToDictionary();
jakoa.ToLookup(selectKeyA)
iadict.OuterGet(key)
jakoalookup[key]
. Pierwsze odbioru kluczy jest trochę trudniejsze, ale:alookup.Select(x => x.Keys)
.Myślę, że są problemy z większością z nich, w tym z zaakceptowaną odpowiedzią, ponieważ nie działają one dobrze z Linq nad IQueryable albo z powodu zbyt dużej liczby podróży w obie strony na serwerze i zbyt dużej liczby zwrotów danych, albo zbyt dużej liczby klientów.
Dla IEnumerable nie podoba mi się odpowiedź Sehe lub podobna, ponieważ ma nadmierne wykorzystanie pamięci (prosty test 10000000 dwóch list uruchomił Linqpad z pamięci na moim komputerze o pojemności 32 GB).
Ponadto większość innych nie wdraża właściwie pełnego pełnego połączenia zewnętrznego, ponieważ używają Unii z prawym złączem zamiast Concat z prawym łączeniem anty-częściowym, co nie tylko eliminuje duplikaty wewnętrznych rzędów złączeń z wyniku, ale także wszelkie prawidłowe duplikaty istniejące pierwotnie w danych po lewej lub po prawej stronie.
Oto moje rozszerzenia, które obsługują wszystkie te problemy, generują SQL, a także implementują bezpośrednie dołączanie do LINQ do SQL, uruchamiają się na serwerze i są szybsze i mają mniej pamięci niż inne na Enumerables:
Różnica między prawym łączeniem częściowym jest głównie dyskusyjna z Linq do Objects lub w źródle, ale robi różnicę po stronie serwera (SQL) w ostatecznej odpowiedzi, usuwając niepotrzebne
JOIN
.Ręczne kodowanie
Expression
obsługi połączeniaExpression<Func<>>
LinqKit może usprawnić w lambda, ale byłoby miło, gdyby język / kompilator dodał do tego jakąś pomoc. FunkcjeFullOuterJoinDistinct
iRightOuterJoin
są włączone dla kompletności, ale nie wdrożyłemFullOuterGroupJoin
jeszcze.napisałem inną wersję pełnego sprzężenia zewnętrznego dla
IEnumerable
dla przypadków, w których klucz można zamówić, co jest około 50% szybsze niż połączenie lewego sprzężenia zewnętrznego z prawym łączeniem anty-pół, przynajmniej w małych kolekcjach. Przechodzi przez każdą kolekcję po sortowaniu tylko raz.Dodałem także inną odpowiedź dla wersji, która działa z EF, zastępując
Invoke
niestandardowe rozszerzenie.źródło
TP unusedP, TC unusedC
? Czy są dosłownie nieużywane?TP
,TC
,TResult
aby stworzyć właściwyExpression<Func<>>
. Mam mogę je zastąpić_
,__
,___
zamiast, ale to nie wydaje się być jaśniejsze, aż C # ma odpowiednią wieloznacznego zamiast parametru do używania.The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
. Czy są jakieś ograniczenia związane z tym kodem? Chcę wykonać PEŁNE DOŁĄCZENIE do IQueryablesInvoke
niestandardową,ExpressionVisitor
aby wstawić,Invoke
więc powinna działać z EF. Możesz spróbowaćOto metoda rozszerzenia, która to robi:
źródło
Union
usuwa duplikaty, więc jeśli w oryginalnych danych są zduplikowane wiersze, nie będzie ich rezultatem.Zgaduję, że podejście @ sehe jest silniejsze, ale dopóki nie zrozumiem go lepiej, odkrywam, że przeskakuję nad rozszerzeniem @ MichaelSander. Zmodyfikowałem go, aby dopasować składnię i typ zwracanej wbudowanej metody Enumerable.Join () opisanej tutaj . Dołączyłem „wyraźny” przyrostek w odniesieniu do komentarza @ cadrell0 pod rozwiązaniem @ JeffMercado.
W tym przykładzie użyłbyś tego w następujący sposób:
W przyszłości, gdy będę się więcej uczyć, mam wrażenie, że migruję do logiki @ sehe, biorąc pod uwagę jej popularność. Ale nawet wtedy będę musiał zachować ostrożność, ponieważ uważam, że ważne jest, aby mieć co najmniej jedno przeciążenie, które pasuje do składni istniejącej metody „.Join ()”, jeśli jest to możliwe, z dwóch powodów:
Nadal jestem nowy z ogólnymi, rozszerzeniami, instrukcjami Func i innymi funkcjami, więc opinie są z pewnością mile widziane.
EDYTOWAĆ: Nie zajęło mi długo uświadomienie sobie, że wystąpił problem z moim kodem. Robiłem .Dump () w LINQPad i szukałem typu zwracanego. To było po prostu niezliczone, więc próbowałem to dopasować. Ale kiedy faktycznie zrobiłem .Where () lub .Select () na moim rozszerzeniu, dostałem błąd: „System Collection.IEnumerable” nie zawiera definicji „Select” i… ”. W końcu udało mi się dopasować składnię wejściową .Join (), ale nie zachowanie powrotu.
EDYCJA: Dodano „TResult” do typu zwracanego dla funkcji. Pominął to podczas czytania artykułu Microsoft i oczywiście ma to sens. Dzięki tej poprawce wydaje się, że zachowanie powrotu jest zgodne z moimi celami.
źródło
Jak już odkryłeś, Linq nie ma konstrukcji „łączenia zewnętrznego”. Najbliższe, jakie możesz uzyskać, to lewe połączenie zewnętrzne za pomocą podanego zapytania. Do tego możesz dodać dowolne elementy listy nazwisk, które nie są reprezentowane w złączeniu:
źródło
Podoba mi się odpowiedź sehe, ale nie wykorzystuje ona odroczonego wykonania (sekwencje wejściowe są chętnie wyliczane przez wywołania ToLookup). Po przejrzeniu źródeł .NET dla LINQ-to-objects , wymyśliłem:
Ta implementacja ma następujące ważne właściwości:
Te właściwości są ważne, ponieważ są tym, czego oczekuje ktoś nowy w FullOuterJoin, ale doświadczony w LINQ.
źródło
Postanowiłem dodać to jako osobną odpowiedź, ponieważ nie jestem pewien, czy jest wystarczająco przetestowany. Jest to ponowna implementacja
FullOuterJoin
metody przy użyciu zasadniczo uproszczonej, dostosowanej wersjiLINQKit
Invoke
/Expand
forExpression
, aby działała w Entity Framework. Nie ma wielu wyjaśnień, ponieważ jest prawie taka sama jak moja poprzednia odpowiedź.źródło
base.Visit(node)
nie powinien rzucać wyjątku, ponieważ po prostu powraca w dół drzewa. Mogę uzyskać dostęp do praktycznie każdej usługi udostępniania kodu, ale nie mogę skonfigurować testowej bazy danych. Jednak uruchomienie go z moim testem LINQ to SQL działa dobrze.Guid
klucza z kluczemGuid?
obcym?Wykonuje wyliczenie strumieniowania w pamięci na obu wejściach i wywołuje selektor dla każdego wiersza. Jeśli w bieżącej iteracji nie ma korelacji, jeden z argumentów selektora będzie pusty .
Przykład:
Wymaga IComparer dla typu korelacji, używa funkcji Comparer.Default, jeśli nie jest podana.
Wymaga zastosowania „OrderBy” do wejściowych elementów wyliczeniowych
źródło
OrderBy
oba kluczowe projekcje.OrderBy
buforuje całą sekwencję, z oczywistych powodów .Moje czyste rozwiązanie dla sytuacji, w której klucz jest unikalny w obu wyliczeniach:
więc
wyjścia:
źródło
Pełne sprzężenie zewnętrzne dla dwóch lub więcej tabel: Najpierw rozpakuj kolumnę, do której chcesz dołączyć.
Następnie użyj lewego połączenia zewnętrznego między wyodrębnioną kolumną a tabelami głównymi.
źródło
Napisałem tę klasę rozszerzeń dla aplikacji, być może 6 lat temu, i używam jej odtąd w wielu rozwiązaniach bez problemów. Mam nadzieję, że to pomoże.
edycja: Zauważyłem, że niektórzy mogą nie wiedzieć, jak korzystać z klasy rozszerzenia.
Aby użyć tej klasy rozszerzenia, po prostu odwołaj się do jej przestrzeni nazw w swojej klasie, dodając następujący wiersz za pomocą joinext;
^ to powinno pozwolić ci zobaczyć inteligencję funkcji rozszerzeń w dowolnej kolekcji obiektów IEnumerable, której używasz.
Mam nadzieję że to pomoże. Daj mi znać, jeśli nadal nie jest jasne, i mam nadzieję, że napiszę przykładowy przykład, jak go używać.
Oto klasa:
źródło
SelectMany
nie można przekonwertować na drzewo wyrażeń godne LINQ2SQL.Myślę, że klauzula łączenia LINQ nie jest właściwym rozwiązaniem tego problemu, ponieważ celem klauzuli przyłączenia nie jest gromadzenie danych w sposób wymagany dla tego rozwiązania zadania. Kod do scalania utworzonych oddzielnych kolekcji staje się zbyt skomplikowany, być może jest odpowiedni do celów edukacyjnych, ale nie do prawdziwych aplikacji. Jednym ze sposobów rozwiązania tego problemu jest poniższy kod:
Jeśli rzeczywiste kolekcje są duże do tworzenia HashSet, zamiast tego można użyć pętli foreach:
źródło
Dziękujemy wszystkim za ciekawe posty!
Zmodyfikowałem kod, ponieważ w moim przypadku potrzebowałem
Dla zainteresowanych jest to mój zmodyfikowany kod (w VB, przepraszam)
źródło
Kolejne pełne zewnętrzne połączenie
Ponieważ nie byłem zadowolony z prostoty i czytelności innych zdań, skończyłem z tym:
Nie ma pretensji, aby być szybkim (około 800 ms, aby dołączyć do 1000 * 1000 na procesorze 2020m: 2,4 GHz / 2 rdzenie). Dla mnie to tylko kompaktowe i swobodne pełne połączenie zewnętrzne.
Działa tak samo jak SQL FULL OUTER JOIN (ochrona duplikatów)
Twoje zdrowie ;-)
Chodzi o to, aby
Oto zwięzły test, który się z tym wiąże:
Umieść punkt przerwania na końcu, aby ręcznie sprawdzić, czy zachowuje się zgodnie z oczekiwaniami
}
źródło
Naprawdę nienawidzę tych wyrażeń linq, dlatego SQL istnieje:
Utwórz to jako widok SQL w bazie danych i zaimportuj jako encję.
Oczywiście (wyraźne) połączenie lewych i prawych złączeń też sprawi, że będzie, ale to jest głupie.
źródło