Jak wykonywać łączenia w LINQ na wielu polach w jednym złączeniu


244

Muszę wykonać zapytanie LINQ2DataSet, które wykonuje sprzężenie na więcej niż jednym polu (as

var result = from x in entity
join y in entity2 
       on x.field1 = y.field1 
and 
          x.field2 = y.field2

Znalazłem jeszcze odpowiednie rozwiązanie (mogę dodać dodatkowe ograniczenia do klauzuli where, ale jest to dalekie od odpowiedniego rozwiązania lub użyć tego rozwiązania, ale zakłada ono ekwiwalinę).

Czy w LINQ jest możliwe łączenie wielu pól w jednym złączeniu?

EDYTOWAĆ

var result = from x in entity
             join y in entity2
             on new { x.field1, x.field2 } equals new { y.field1, y.field2 }

jest rozwiązaniem, o którym wspomniałem powyżej, zakładając ekwiwalent.

Dalsza edycja

Aby odpowiedzieć na krytykę, że mój oryginalny przykład był ekwioinem, potwierdzam, że moim obecnym wymogiem jest ekwiojon i już skorzystałem z rozwiązania, o którym wspominałem powyżej.

Staram się jednak zrozumieć, jakie możliwości i najlepsze praktyki mam / powinnam zastosować w LINQ. Niedługo będę musiał wykonać zapytanie dotyczące zakresu dat z identyfikatorem tabeli, a ja tylko uprzedziłem ten problem. Wygląda na to, że będę musiał dodać zakres dat w klauzuli where.

Dziękujemy, jak zawsze, za wszystkie sugestie i komentarze


48
Po prostu informacja dla każdego, kto to czyta, jeśli łączysz wiele pól w klasach annon, MUSISZ nazwać pola w obu klasach annon tak samo, w przeciwnym razie otrzymasz błędy kompilacji.
Mark

6
A raczej musisz upewnić się, że mają pasujące nazwy. tzn. możesz po prostu nazwać pola jednego z typów anonów, aby dopasować je do drugiego.
Tom Ferguson

1
Zwróć uwagę na tę odpowiedź stackoverflow.com/a/34176502/1704458
TS

Użyłem krotek po obu stronach równych, a nie obiektów, i to też zdawało się działać.
GHZ

Odpowiedzi:


89

Rozwiązanie z typem anonimowym powinno działać dobrze. LINQ może reprezentować tylko ekwizyty (w każdym razie z klauzulami przyłączenia), i tak naprawdę powiedziałeś, że i tak chcesz wyrazić na podstawie oryginalnego zapytania.

Jeśli nie podoba ci się wersja z typem anonimowym z jakiegoś konkretnego powodu, powinieneś to wyjaśnić.

Jeśli chcesz zrobić coś innego niż pierwotnie poprosiłeś, podaj przykład tego, co naprawdę chcesz zrobić.

EDYCJA: Odpowiadając na edycję w pytaniu: tak, aby wykonać łączenie „zakresu dat”, należy zamiast tego użyć klauzuli where. Są naprawdę semantycznie równoważne, więc to tylko kwestia dostępnych optymalizacji. Equijoins zapewniają prostą optymalizację (w LINQ do Objects, która obejmuje LINQ do DataSets) poprzez utworzenie wyszukiwania w oparciu o wewnętrzną sekwencję - pomyśl o tym jako o tablicy mieszającej od klucza do sekwencji wpisów pasujących do tego klucza.

Robienie tego z zakresami dat jest nieco trudniejsze. Jednak w zależności od dokładnie tego, co rozumiesz przez „dołączanie zakresu dat”, możesz być w stanie zrobić coś podobnego - jeśli planujesz utworzyć „pasma” dat (np. Jeden rocznie), tak że dwa wpisy, które występują w ten sam rok (ale nie w tym samym dniu) powinien pasować, możesz to zrobić, używając tego pasma jako klucza. Jeśli jest to bardziej skomplikowane, np. Jedna strona sprzężenia podaje zakres, a druga strona sprzężenia podaje pojedynczą datę, pasującą, jeśli mieści się w tym zakresie, lepiej byłoby użyć whereklauzuli (po drugiejfromklauzula) IMO. Możesz wykonać jakąś szczególnie funkową magię, zlecając jednej lub drugiej stronie bardziej efektywne wyszukiwanie meczów, ale byłoby to dużo pracy - zrobiłbym to tylko po sprawdzeniu, czy wydajność jest problemem.


Dzięki, tak, wydajność jest moim głównym zmartwieniem z użyciem klauzuli where. Zgaduję, że klauzula where po sprzężeniu wykonałaby filtr w większym zbiorze danych, który można by zmniejszyć, wprowadzając drugi parametr łączenia. Podoba mi się pomysł zamawiania w celu przetestowania, czy mogę uzyskać wzrost wydajności
John

Ile rekordów będziesz miał? Nie zapominaj, że uporządkowanie wyników na początek zajmie pewną ilość czasu na początek ...
Jon Skeet

„Są naprawdę semantycznie równoważne” - czy potrzebujemy tam słowa „naprawdę”? Być może miałeś na myśli: „Są naprawdę semantycznie równoważne” :)
dniu

136
var result = from x in entity
   join y in entity2 on new { x.field1, x.field2 } equals new { y.field1, y.field2 }

Właśnie tego szukałem, ponieważ 101 próbek Linq tego nie miało, a przynajmniej widziałem.
Chris Marisic,

1
@PeterX rzeczywiście może, zobacz moją odpowiedź tutaj: stackoverflow.com/a/22176658/595157
niieani

13
Powyższy kod nie działał. Po dodaniu on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 } zadziałało
Ravi Ram,

@Ravi Ram .. Dzięki .. Twój komentarz pomógł
NMathur

80
var result = from x in entity1
             join y in entity2
             on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 }

Musisz to zrobić, jeśli nazwy kolumn są różne w dwóch jednostkach.


6
Dziękujemy za podanie różnych nazw kolumn. To naprawiło mój zły wyraz.
Gaʀʀʏ

1
To też działało dla mnie. Jeśli nazwy kolumn nie pasują, pojawi się błąd: „Typ jednego z wyrażeń w klauzuli łączenia jest niepoprawny. Wywołanie typu nie powiodło się w wywołaniu„ GroupJoin ”.”
humbads

Dziękujemy za aliasing kluczowych zmiennych.
Thomas.Benz

Wystąpił błąd, o którym wspomniał @humbads, gdy nie nazwałem wszystkich właściwości int „new {}”. Więc po prostu fyi, jeśli nazwiesz jedną, musisz również wymienić resztę.
Ethan Melamed

DZIĘKUJĘ WIĘCEJ
Charly H

51

Aby zakończyć to równoważną składnią łańcucha metod:

entity.Join(entity2, x => new {x.Field1, x.Field2},
                     y => new {y.Field1, y.Field2}, (x, y) => x);

Podczas gdy ostatni argument (x, y) => xjest tym, który wybierasz (w powyższym przypadku wybieramy x).


31

Myślę, że bardziej czytelną i elastyczną opcją jest użycie funkcji Where:

var result = from x in entity1
             from y in entity2
                 .Where(y => y.field1 == x.field1 && y.field2 == x.field2)

Pozwala to również na łatwą zmianę z łączenia wewnętrznego na lewe, dodając funkcję .DefaultIfEmpty ().


Jako długoletni użytkownik lambda (w przeciwieństwie do tego, kiedy zadałem to pytanie), musiałbym się zgodzić
John

Czy byłoby to wolniejsze?
AlfredBr

1
Myślę, że powinna mieć taką samą wydajność jak nowa { ... } equals new { ... }składnia. LinqPad to świetne narzędzie do sprawdzania, jak zachowują się wyrażenia (skrypt SQL, jeśli używany jest LINQ2SQL, drzewa wyrażeń itp.)
Alexei,

O ile zauważyłem, produkuje CROSS JOIN zamiast INNER JOIN
Mariusz

@Mariusz Tak, sensowne jest wygenerowanie CROSS JOIN + WHERE zamiast INNER JOIN. W przypadku prostych zapytań oczekuję jednak, że analizator wygeneruje bardzo podobny.
Aleksiej

10
var result = from x in entity
             join y in entity2
             on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 }
             select new 
             {
               /// Columns
              };

8

możesz zrobić coś takiego (poniżej)

var query = from p in context.T1

        join q in context.T2

        on

        new { p.Col1, p.Col2 }

        equals

         new { q.Col1, q.Col2 }

        select new {p...., q......};

Jak wspomniałem w pytaniu, wymaga to ekwipunku
John

7

Za pomocą operatora łączyć możesz wykonywać tylko ekwiwiny. Inne typy złączeń można konstruować przy użyciu innych operatorów. Nie jestem pewien, czy dokładne połączenie, które próbujesz wykonać, byłoby łatwiejsze przy użyciu tych metod lub poprzez zmianę klauzuli where. Dokumentację dotyczącą klauzuli przyłączenia można znaleźć tutaj . MSDN ma również artykuł na temat operacji łączenia z wieloma linkami do przykładów innych połączeń.


3

Jeśli nazwy pól są różne w jednostkach

var result = from x in entity
   join y in entity2 on 
          new {
                field1=   x.field1,
               field2 =  x.field2 
             } 
          equals
         new { 
                field1= y.field1,
                field2=  y.myfield
              }
select new {x,y});

Dziękuję Ci. Dopasowanie nazwy było tym, czego mi brakowało.
Brett,

2

Jako pełny łańcuch metod, który wyglądałby tak:

lista.SelectMany(a => listb.Where(xi => b.Id == a.Id && b.Total != a.Total),
                (a, b) => new ResultItem
                {
                    Id = a.Id,
                    ATotal = a.Total,
                    BTotal = b.Total
                }).ToList();

-2
from d in db.CourseDispatches
                             join du in db.DispatchUsers on d.id equals du.dispatch_id
                             join u in db.Users on du.user_id equals u.id
                             join fr in db.Forumreports on (d.course_id + '_' + du.user_id)  equals  (fr.course_id + '_'+ fr.uid)

to działa dla mnie


To jest dla wielokrotnego łączenia, on chce zrobić połączenie z wieloma polami w jednym złączeniu
theLaw

-3

Zadeklaruj klasę (typ), aby pomieścić elementy, które chcesz dołączyć. W poniższym przykładzie zadeklaruj JoinElement

 public class **JoinElement**
{
    public int? Id { get; set; }
    public string Name { get; set; }

}

results = from course in courseQueryable.AsQueryable()
                  join agency in agencyQueryable.AsQueryable()
                   on new **JoinElement**() { Id = course.CourseAgencyId, Name = course.CourseDeveloper } 
                   equals new **JoinElement**() { Id = agency.CourseAgencyId, Name = "D" } into temp1

1
Odpowiedzi na to już 9 lat temu ... jaką wartość ma ta odpowiedź?
Maciej Jureczko
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.