DOŁĄCZ DO ZEWNĘTRZNEJ LINK


538

Jak wykonać lewe połączenie zewnętrzne w C # LINQ do obiektów bez użycia join-on-equals-intoklauzul? Czy można to zrobić za pomocą whereklauzuli? Prawidłowy problem: łączenie wewnętrzne jest łatwe i mam takie rozwiązanie

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})

ale dla lewego złączenia zewnętrznego potrzebuję rozwiązania. Mój jest coś takiego, ale nie działa

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
                                        })

gdzie JoinPair jest klasą:

public class JoinPair { long leftId; long rightId; }

2
czy możesz podać przykład tego, co próbujesz osiągnąć?
jeroenh

normalne lewe łączenie zewnętrzne wygląda mniej więcej tak: var a = zb w bb łączenie cw cc na b.bbbbb równa się c.ccccc w dd z d w dd.DefaultIfEmpty () select b.sss; Moim pytaniem jest, czy można to zrobić za pomocą klauzul Join-on-Equals-into coś takiego: Var a = zb w bb zc w cc gdzie b.bbb == c.cccc ... i tak dalej ... .
Toy

1
pewnie, że jest, ale powinieneś opublikować przykładowy kod, który już masz, aby ludzie mogli dać ci lepszą odpowiedź
leniwość


Odpowiedzi:


598

Jak stwierdzono w:

101 próbek LINQ - lewe połączenie zewnętrzne

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
Próbuję tego samego, ale pojawia się błąd operatora łączenia, który mówi: „Typ jednego z wyrażeń w klauzuli łączenia jest niepoprawny”.
Badhon Jain

3
@jain, jeśli Twoje typy są różne, łączenie nie będzie działać. Więc prawdopodobnie twoje klucze mają różne typy danych. Czy oba klucze są na przykład int?
Yooakim

2
Jakie rozwiązanie Jain? Występuje również ten sam błąd, a typy również są takie same w moim przypadku.
Sandeep

1
@ Sandeep sprawdź klucze tam, gdzie do nich dołączyłeś. Załóżmy, że jeśli są typu string i int, po prostu przekonwertuj klucz string na int.
Ankit


546

Jeśli używany jest dostawca LINQ oparty na bazie danych, można znacznie lepiej odczytać lewe łączenie zewnętrzne:

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

Jeśli pominiesz to DefaultIfEmpty(), będziesz mieć wewnętrzne połączenie.

Weź przyjętą odpowiedź:

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

Ta składnia jest bardzo myląca i nie jest jasne, jak to działa, gdy chcesz opuścić dołączanie do WIELU tabel.

Uwaga
Należy zauważyć, żefrom alias in Repo.whatever.Where(condition).DefaultIfEmpty() jest to to samo, co zewnętrzny-zastosuj / lewy-złącz-boczny, który dowolny (porządny) optymalizator bazy danych jest w stanie doskonale przetłumaczyć na lewe złączenie, o ile nie wprowadzisz poszczególnych wierszy -wartości (inaczej rzeczywiste zastosowanie zewnętrzne). Nie rób tego w Linq-2-Objects (ponieważ nie ma optymalizatora DB, gdy używasz Linq-to-Objects).

Szczegółowy przykład

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

W połączeniu z LINQ 2 SQL ładnie przełoży się na następujące bardzo czytelne zapytanie SQL:

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

Edytować:

Zobacz także „ Konwertuj zapytanie SQL Server na zapytanie Linq ”, aby uzyskać bardziej złożony przykład.

Ponadto, jeśli robisz to w Linq-2-Objects (zamiast Linq-2-SQL), powinieneś to zrobić w staromodny sposób (ponieważ LINQ na SQL tłumaczy to poprawnie, aby połączyć operacje, ale nad obiektami ta metoda wymusza pełne skanowanie i nie korzysta z wyszukiwania indeksów, cokolwiek ...):

    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
    }

);

21
Ta odpowiedź jest w rzeczywistości pomocna. Dziękujemy za zaoferowanie zrozumiałej składni.
Chris Marisic

3
WTB zapytanie LINQ kompatybilne z NHibernate ... :)
mxmissile,

30
LINQ na SQL tłumaczy to poprawnie, aby połączyć operacje. Jednak nad obiektami ta metoda wymusza pełne skanowanie, dlatego oficjalna dokumentacja oferuje rozwiązanie łączenia grup, które może wykorzystywać skróty do wyszukiwania indeksów.
Tamir Daniely,

3
Myślę, że składnia joinwhereDefaultIfEmpty
wyrażeń

1
@ user3441905: Tak długo, jak wystarczy dołączyć do tabeli a tabelą b, może tak być. Ale gdy tylko będziesz mieć więcej, nie będzie. Ale nawet dla dwóch stolików myślę, że jest to zbyt werbalne. Popularna opinia również wydaje się być przeciwko tobie, ponieważ ta odpowiedź rozpoczęła się od 0, gdy najwyższa odpowiedź miała już ponad 90 głosów pozytywnych.
Stefan Steiger,

131

Korzystanie z wyrażenia lambda

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

8
Zarówno Join, jak i GroupJoin tak naprawdę nie obsługują dołączenia do lewej. Sztuką przy użyciu GroupJoin jest to, że możesz mieć puste grupy, a następnie tłumaczyć te puste grupy na puste wartości. DefaultIfEmpty po prostu to robi, co oznacza, Enumerable.Empty<Product>.DefaultIfEmpty()że zwróci IEnumerable z pojedynczą wartością default(Product).
Tamir Daniely,

61
Wszystko po to, by wykonać lewe łączenie?
FindOut_Quran,

7
Dzięki za to! Nie ma zbyt wielu przykładów wyrażeń lambda, to zadziałało dla mnie.
Johan Henkens

1
Dziękuję za odpowiedź. Dało to wynik najbliższy nieprzetworzonemu SQL LEFT OUTER JOIN, który napisałem przez lata
John Gathogo

1
Naprawdę nie potrzebuję ostatniego Select (), anon obj w SelectMany () można refaktoryzować dla tego samego wyjścia. Inną myślą jest przetestowanie y pod kątem wartości null, aby zasymulować bliższą LEWĄ JOIN równoważność.
Denny Jacob,

46

Teraz jako metoda rozszerzenia:

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

Używaj tak, jak normalnie używasz łączenia:

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

Mam nadzieję, że to pozwoli Ci zaoszczędzić trochę czasu.


44

Spójrz na ten przykład . To zapytanie powinno działać:

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
Czy rmożna uzyskać dostęp w klauzuli select po użyciu przyłączenia do?
Farhad Alizadeh Noori

@FarhadAlizadehNoori Tak, może.
Po-ta-toe

Autor prawdopodobnie zamierzał ponownie użyć rw drugiej fromklauzuli. tzn. w from r in lrs.DefaultIfEmpty()przeciwnym razie to zapytanie nie ma większego sensu i prawdopodobnie nawet się nie kompiluje z powodu rbraku kontekstu dla zaznaczenia.
Saeb Amini,

@Devart, kiedy przeczytałem twoje zapytanie, przypomniało mi to film Clockwisez Johnem Cleese, lol.
Matas Vaitkevicius,

1
Od lewej do prawej w lewe prawa w lewej po lewej w prawej ... O Jezu ... Składnia użycia LEFT OUTER JOIN w LINQ naprawdę nie jest jasna, ale te nazwy naprawdę jeszcze bardziej niejasne.
Mike Gledhill

19

Może wyglądać implementacja lewego łączenia zewnętrznego metodami rozszerzenia

public static IEnumerable<Result> LeftJoin<TOuter, TInner, TKey, Result>(
  this IEnumerable<TOuter> outer, IEnumerable<TInner> inner
  , Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
  , Func<TOuter, TInner, Result> resultSelector, IEqualityComparer<TKey> comparer)
  {
    if (outer == null)
      throw new ArgumentException("outer");

    if (inner == null)
      throw new ArgumentException("inner");

    if (outerKeySelector == null)
      throw new ArgumentException("outerKeySelector");

    if (innerKeySelector == null)
      throw new ArgumentException("innerKeySelector");

    if (resultSelector == null)
      throw new ArgumentException("resultSelector");

    return LeftJoinImpl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer ?? EqualityComparer<TKey>.Default);
  }

  static IEnumerable<Result> LeftJoinImpl<TOuter, TInner, TKey, Result>(
      IEnumerable<TOuter> outer, IEnumerable<TInner> inner
      , Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
      , Func<TOuter, TInner, Result> resultSelector, IEqualityComparer<TKey> comparer)
  {
    var innerLookup = inner.ToLookup(innerKeySelector, comparer);

    foreach (var outerElment in outer)
    {
      var outerKey = outerKeySelector(outerElment);
      var innerElements = innerLookup[outerKey];

      if (innerElements.Any())
        foreach (var innerElement in innerElements)
          yield return resultSelector(outerElment, innerElement);
      else
        yield return resultSelector(outerElment, default(TInner));
     }
   }

Następnie wynikowy selektor musi zająć się zerowymi elementami. Fx.

   static void Main(string[] args)
   {
     var inner = new[] { Tuple.Create(1, "1"), Tuple.Create(2, "2"), Tuple.Create(3, "3") };
     var outer = new[] { Tuple.Create(1, "11"), Tuple.Create(2, "22") };

     var res = outer.LeftJoin(inner, item => item.Item1, item => item.Item1, (it1, it2) =>
     new { Key = it1.Item1, V1 = it1.Item2, V2 = it2 != null ? it2.Item2 : default(string) });

     foreach (var item in res)
       Console.WriteLine(string.Format("{0}, {1}, {2}", item.Key, item.V1, item.V2));
   }

4
Jest to jednak tylko opcja dla obiektów LINQ i nie będzie mogła przetłumaczyć zapytania na żadnego dostawcę zapytania, co jest najczęstszym przypadkiem użycia tej operacji.
Servy

13
Ale pytanie brzmiało: „Jak wykonać lewe zewnętrzne połączenie w C # LINQ do obiektów ...”
Bertrand

12

spójrz na ten przykład

class Person
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Phone { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

public static void LeftOuterJoinExample()
{
    Person magnus = new Person {ID = 1, FirstName = "Magnus", LastName = "Hedlund"};
    Person terry = new Person {ID = 2, FirstName = "Terry", LastName = "Adams"};
    Person charlotte = new Person {ID = 3, FirstName = "Charlotte", LastName = "Weiss"};
    Person arlene = new Person {ID = 4, FirstName = "Arlene", LastName = "Huff"};

    Pet barley = new Pet {Name = "Barley", Owner = terry};
    Pet boots = new Pet {Name = "Boots", Owner = terry};
    Pet whiskers = new Pet {Name = "Whiskers", Owner = charlotte};
    Pet bluemoon = new Pet {Name = "Blue Moon", Owner = terry};
    Pet daisy = new Pet {Name = "Daisy", Owner = magnus};

    // Create two lists.
    List<Person> people = new List<Person> {magnus, terry, charlotte, arlene};
    List<Pet> pets = new List<Pet> {barley, boots, whiskers, bluemoon, daisy};

    var query = from person in people
        where person.ID == 4
        join pet in pets on person equals pet.Owner  into personpets
        from petOrNull in personpets.DefaultIfEmpty()
        select new { Person=person, Pet = petOrNull}; 



    foreach (var v in query )
    {
        Console.WriteLine("{0,-15}{1}", v.Person.FirstName + ":", (v.Pet == null ? "Does not Exist" : v.Pet.Name));
    }
}

// This code produces the following output:
//
// Magnus:        Daisy
// Terry:         Barley
// Terry:         Boots
// Terry:         Blue Moon
// Charlotte:     Whiskers
// Arlene:

teraz możesz, include elements from the leftnawet jeśli ten element has no matches in the right, w naszym przypadku wycofaliśmy Arlenenawet on nie ma pasującego odpowiednika

tutaj jest odniesienie

Instrukcje: wykonywanie lewych połączeń zewnętrznych (przewodnik programowania w języku C #)


dane wyjściowe powinny być następujące: Arlene: nie istnieje
użytkownik1169587,

10

To jest forma ogólna (jak już podano w innych odpowiedziach)

var c =
    from a in alpha
    join b in beta on b.field1 equals a.field1 into b_temp
    from b_value in b_temp.DefaultIfEmpty()
    select new { Alpha = a, Beta = b_value };

Oto wyjaśnienie, które mam nadzieję wyjaśni, co to właściwie oznacza!

join b in beta on b.field1 equals a.field1 into b_temp

zasadniczo tworzy osobny zestaw wyników b_temp, który skutecznie zawiera null „wiersze” dla wpisów po prawej stronie (wpisy w „b”).

Następnie następny wiersz:

from b_value in b_temp.DefaultIfEmpty()

.. powtarza ten zestaw wyników, ustawiając domyślną wartość zerową dla „wiersza” po prawej stronie i ustawiając wynik łączenia wiersza po prawej stronie na wartość „b_value” (tj. wartość znajdującą się po prawej stronie strona strony, jeśli istnieje pasujący rekord lub „null”, jeśli nie ma).

Teraz, jeśli prawa strona jest wynikiem oddzielnego zapytania LINQ, będzie się składać z anonimowych typów, które mogą być albo „czymś”, albo „zerą”. Jeśli jednak jest to wyliczenie (np. Lista - gdzie MyObjectB jest klasą z 2 polami), można sprecyzować, jakie domyślne wartości „null” są używane dla jego właściwości:

var c =
    from a in alpha
    join b in beta on b.field1 equals a.field1 into b_temp
    from b_value in b_temp.DefaultIfEmpty( new MyObjectB { Field1 = String.Empty, Field2 = (DateTime?) null })
    select new { Alpha = a, Beta_field1 = b_value.Field1, Beta_field2 = b_value.Field2 };

Zapewnia to, że samo „b” nie ma wartości NULL (ale jego właściwości mogą być zerowe, przy użyciu domyślnych wartości NULL, które określiłeś), a to pozwala ci sprawdzać właściwości wartości b_value bez uzyskania wyjątku odniesienia NULL dla b_value. Należy zauważyć, że w przypadku zerowej wartości DateTime typ (DateTime?), Tj. „Zerowy DateTime”, musi być określony jako „Typ” wartości null w specyfikacji dla „DefaultIfEmpty” (będzie to również dotyczyło typów, które nie są „natywnie” nullable np. double, float).

Możesz wykonać wiele lewych złączeń zewnętrznych, po prostu łącząc powyższą składnię.


1
skąd pochodzi wartość b?
Jack Fraser

9

Oto przykład, jeśli chcesz dołączyć do więcej niż 2 tabel:

from d in context.dc_tpatient_bookingd
join bookingm in context.dc_tpatient_bookingm 
     on d.bookingid equals bookingm.bookingid into bookingmGroup
from m in bookingmGroup.DefaultIfEmpty()
join patient in dc_tpatient
     on m.prid equals patient.prid into patientGroup
from p in patientGroup.DefaultIfEmpty()

Patrz: https://stackoverflow.com/a/17142392/2343


4

Metoda rozszerzenia, która działa jak lewy łączenie ze składnią Join

public static class LinQExtensions
{
    public static IEnumerable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
        this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, 
        Func<TOuter, TKey> outerKeySelector, 
        Func<TInner, TKey> innerKeySelector, 
        Func<TOuter, TInner, TResult> resultSelector)
    {
        return outer.GroupJoin(
            inner, 
            outerKeySelector, 
            innerKeySelector,
            (outerElement, innerElements) => resultSelector(outerElement, innerElements.FirstOrDefault()));
    }
}

właśnie napisałem to w .NET core i wydaje się, że działa zgodnie z oczekiwaniami.

Mały test:

        var Ids = new List<int> { 1, 2, 3, 4};
        var items = new List<Tuple<int, string>>
        {
            new Tuple<int, string>(1,"a"),
            new Tuple<int, string>(2,"b"),
            new Tuple<int, string>(4,"d"),
            new Tuple<int, string>(5,"e"),
        };

        var result = Ids.LeftJoin(
            items,
            id => id,
            item => item.Item1,
            (id, item) => item ?? new Tuple<int, string>(id, "not found"));

        result.ToList()
        Count = 4
        [0]: {(1, a)}
        [1]: {(2, b)}
        [2]: {(3, not found)}
        [3]: {(4, d)}

4

Oto dość łatwa do zrozumienia wersja wykorzystująca składnię metody:

IEnumerable<JoinPair> outerLeft =
    lefts.SelectMany(l => 
        rights.Where(r => l.Key == r.Key)
              .DefaultIfEmpty(new Item())
              .Select(r => new JoinPair { LeftId = l.Id, RightId = r.Id }));

3

Istnieją trzy tabele: osoby, szkoły i osoby_szkolne, które łączą osoby ze szkołami, w których studiują. Odwołanie do osoby o id = 6 jest nieobecne w tabeli osoby_szkolne. Jednak osoba o id = 6 jest prezentowana w wyniku siatki połączonej w lewo.

List<Person> persons = new List<Person>
{
    new Person { id = 1, name = "Alex", phone = "4235234" },
    new Person { id = 2, name = "Bob", phone = "0014352" },
    new Person { id = 3, name = "Sam", phone = "1345" },
    new Person { id = 4, name = "Den", phone = "3453452" },
    new Person { id = 5, name = "Alen", phone = "0353012" },
    new Person { id = 6, name = "Simon", phone = "0353012" }
};

List<School> schools = new List<School>
{
    new School { id = 1, name = "Saint. John's school"},
    new School { id = 2, name = "Public School 200"},
    new School { id = 3, name = "Public School 203"}
};

List<PersonSchool> persons_schools = new List<PersonSchool>
{
    new PersonSchool{id_person = 1, id_school = 1},
    new PersonSchool{id_person = 2, id_school = 2},
    new PersonSchool{id_person = 3, id_school = 3},
    new PersonSchool{id_person = 4, id_school = 1},
    new PersonSchool{id_person = 5, id_school = 2}
    //a relation to the person with id=6 is absent
};

var query = from person in persons
            join person_school in persons_schools on person.id equals person_school.id_person
            into persons_schools_joined
            from person_school_joined in persons_schools_joined.DefaultIfEmpty()
            from school in schools.Where(var_school => person_school_joined == null ? false : var_school.id == person_school_joined.id_school).DefaultIfEmpty()
            select new { Person = person.name, School = school == null ? String.Empty : school.name };

foreach (var elem in query)
{
    System.Console.WriteLine("{0},{1}", elem.Person, elem.School);
}

Być może jest to odpowiedź na pytanie, które zawiera wyjaśnienie dotyczące twojej odpowiedzi :)
Amir

2

Jest to składnia SQL w porównaniu do składni LINQ dla złączeń wewnętrznych i lewych zewnętrznych. Lewy zewnętrzny łącznik:

http://www.ozkary.com/2011/07/linq-to-entity-inner-and-left-joins.html

„W poniższym przykładzie łączenie grupy między produktem i kategorią. Jest to zasadniczo lewe łączenie. Wyrażenie wyrażenia zwraca dane, nawet jeśli tabela kategorii jest pusta. Aby uzyskać dostęp do właściwości tabeli kategorii, musimy teraz wybrać z wyliczalnego wyniku przez dodanie instrukcji from cl w catList.DefaultIfEmpty ().


1

Wykonaj lewe połączenia zewnętrzne w linq C # // Wykonaj lewe połączenia zewnętrzne

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Child
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}
public class JoinTest
{
    public static void LeftOuterJoinExample()
    {
        Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
        Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
        Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
        Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };

        Child barley = new Child { Name = "Barley", Owner = terry };
        Child boots = new Child { Name = "Boots", Owner = terry };
        Child whiskers = new Child { Name = "Whiskers", Owner = charlotte };
        Child bluemoon = new Child { Name = "Blue Moon", Owner = terry };
        Child daisy = new Child { Name = "Daisy", Owner = magnus };

        // Create two lists.
        List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
        List<Child> childs = new List<Child> { barley, boots, whiskers, bluemoon, daisy };

        var query = from person in people
                    join child in childs
                    on person equals child.Owner into gj
                    from subpet in gj.DefaultIfEmpty()
                    select new
                    {
                        person.FirstName,
                        ChildName = subpet!=null? subpet.Name:"No Child"
                    };
                       // PetName = subpet?.Name ?? String.Empty };

        foreach (var v in query)
        {
            Console.WriteLine($"{v.FirstName + ":",-25}{v.ChildName}");
        }
    }

    // This code produces the following output:
    //
    // Magnus:        Daisy
    // Terry:         Barley
    // Terry:         Boots
    // Terry:         Blue Moon
    // Charlotte:     Whiskers
    // Arlene:        No Child

https://dotnetwithhamid.blogspot.in/


1

Chciałbym dodać, że jeśli dostaniesz rozszerzenie MoreLinq, teraz jest teraz obsługiwane zarówno homogeniczne, jak i heterogeniczne lewe połączenia.

http://morelinq.github.io/2.8/ref/api/html/Overload_MoreLinq_MoreEnumerable_LeftJoin.htm

przykład:

//Pretend a ClientCompany object and an Employee object both have a ClientCompanyID key on them

return DataContext.ClientCompany
    .LeftJoin(DataContext.Employees,                         //Table being joined
        company => company.ClientCompanyID,                  //First key
        employee => employee.ClientCompanyID,                //Second Key
        company => new {company, employee = (Employee)null}, //Result selector when there isn't a match
        (company, employee) => new { company, employee });   //Result selector when there is a match

EDYTOWAĆ:

Z perspektywy czasu może to działać, ale konwertuje IQueryable na IEnumerable, ponieważ morelinq nie konwertuje zapytania na SQL.

Zamiast tego możesz użyć GroupJoin, jak opisano tutaj: https://stackoverflow.com/a/24273804/4251433

Zapewni to, że pozostanie jako IQueryable na wypadek, gdybyś musiał później wykonać dalsze operacje logiczne.


1

Prostym sposobem jest użycie słowa kluczowego Let. To działa dla mnie.

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
}

To symulacja Left Join. Jeśli każdy element w tabeli B nie pasuje do elementu A, BItem zwraca wartość null


0

Jeśli chcesz dołączyć i przefiltrować coś, możesz to zrobić poza łączeniem. Filtr można wykonać po utworzeniu kolekcji.

W takim przypadku, jeśli zrobię to w stanie łączenia, zmniejszę liczbę zwracanych wierszy.

Stosowany jest warunek trójskładnikowy (= n == null ? "__" : n.MonDayNote,)

  • Jeśli obiekt jest null(więc nie pasuje), to zwróć to, co następuje po ?. __, w tym przypadku.

  • Innego, co jest wrócić po :, n.MonDayNote.

Dzięki innym autorom, od tego momentu zacząłem od własnego problemu.


        var schedLocations = (from f in db.RAMS_REVENUE_LOCATIONS
              join n in db.RAMS_LOCATION_PLANNED_MANNING on f.revenueCenterID equals

                  n.revenueCenterID into lm

              from n in lm.DefaultIfEmpty()

              join r in db.RAMS_LOCATION_SCHED_NOTE on f.revenueCenterID equals r.revenueCenterID
              into locnotes

              from r in locnotes.DefaultIfEmpty()
              where f.LocID == nLocID && f.In_Use == true && f.revenueCenterID > 1000

              orderby f.Areano ascending, f.Locname ascending
              select new
              {
                  Facname = f.Locname,
                  f.Areano,
                  f.revenueCenterID,
                  f.Locabbrev,

                  //  MonNote = n == null ? "__" : n.MonDayNote,
                  MonNote = n == null ? "__" : n.MonDayNote,
                  TueNote = n == null ? "__" : n.TueDayNote,
                  WedNote = n == null ? "__" : n.WedDayNote,
                  ThuNote = n == null ? "__" : n.ThuDayNote,

                  FriNote = n == null ? "__" : n.FriDayNote,
                  SatNote = n == null ? "__" : n.SatDayNote,
                  SunNote = n == null ? "__" : n.SunDayNote,
                  MonEmpNbr = n == null ? 0 : n.MonEmpNbr,
                  TueEmpNbr = n == null ? 0 : n.TueEmpNbr,
                  WedEmpNbr = n == null ? 0 : n.WedEmpNbr,
                  ThuEmpNbr = n == null ? 0 : n.ThuEmpNbr,
                  FriEmpNbr = n == null ? 0 : n.FriEmpNbr,
                  SatEmpNbr = n == null ? 0 : n.SatEmpNbr,
                  SunEmpNbr = n == null ? 0 : n.SunEmpNbr,
                  SchedMondayDate = n == null ? dMon : n.MondaySchedDate,
                  LocNotes = r == null ? "Notes: N/A" : r.LocationNote

              }).ToList();
                Func<int, string> LambdaManning = (x) => { return x == 0 ? "" : "Manning:" + x.ToString(); };
        DataTable dt_ScheduleMaster = PsuedoSchedule.Tables["ScheduleMasterWithNotes"];
        var schedLocations2 = schedLocations.Where(x => x.SchedMondayDate == dMon);

0
class Program
{
    List<Employee> listOfEmp = new List<Employee>();
    List<Department> listOfDepart = new List<Department>();

    public Program()
    {
        listOfDepart = new List<Department>(){
            new Department { Id = 1, DeptName = "DEV" },
            new Department { Id = 2, DeptName = "QA" },
            new Department { Id = 3, DeptName = "BUILD" },
            new Department { Id = 4, DeptName = "SIT" }
        };


        listOfEmp = new List<Employee>(){
            new Employee { Empid = 1, Name = "Manikandan",DepartmentId=1 },
            new Employee { Empid = 2, Name = "Manoj" ,DepartmentId=1},
            new Employee { Empid = 3, Name = "Yokesh" ,DepartmentId=0},
            new Employee { Empid = 3, Name = "Purusotham",DepartmentId=0}
        };

    }
    static void Main(string[] args)
    {
        Program ob = new Program();
        ob.LeftJoin();
        Console.ReadLine();
    }

    private void LeftJoin()
    {
        listOfEmp.GroupJoin(listOfDepart.DefaultIfEmpty(), x => x.DepartmentId, y => y.Id, (x, y) => new { EmpId = x.Empid, EmpName = x.Name, Dpt = y.FirstOrDefault() != null ? y.FirstOrDefault().DeptName : null }).ToList().ForEach
            (z =>
            {
                Console.WriteLine("Empid:{0} EmpName:{1} Dept:{2}", z.EmpId, z.EmpName, z.Dpt);
            });
    }
}

class Employee
{
    public int Empid { get; set; }
    public string Name { get; set; }
    public int DepartmentId { get; set; }
}

class Department
{
    public int Id { get; set; }
    public string DeptName { get; set; }
}

WYNIK


0

Zgodnie z moją odpowiedzią na podobne pytanie tutaj:

Lewe łączenie zewnętrzne Linq-SQL za pomocą składni Lambda i łączenie na 2 kolumnach (kompozytowy klucz łączenia)

Pobierz kod tutaj lub sklonuj moje repozytorium github i graj!

Pytanie:

        var petOwners =
            from person in People
            join pet in Pets
            on new
            {
                person.Id,
                person.Age,
            }
            equals new
            {
                pet.Id,
                Age = pet.Age * 2, // owner is twice age of pet
            }
            into pets
            from pet in pets.DefaultIfEmpty()
            select new PetOwner
            {
                Person = person,
                Pet = pet,
            };

Lambda:

        var petOwners = People.GroupJoin(
            Pets,
            person => new { person.Id, person.Age },
            pet => new { pet.Id, Age = pet.Age * 2 },
            (person, pet) => new
            {
                Person = person,
                Pets = pet,
            }).SelectMany(
            pet => pet.Pets.DefaultIfEmpty(),
            (people, pet) => new
            {
                people.Person,
                Pet = pet,
            });

0

Omówienie: W tym fragmencie kodu pokazuję, jak grupować według identyfikatora, gdzie Tabela 1 i Tabela 2 mają relację jeden do wielu. Grupuję według Id, Field1 i Field2. Podkwerenda jest pomocna, jeśli wymagane jest trzecie wyszukiwanie w Tabeli i wymagałoby relacji łączenia po lewej stronie. Pokazuję lewe grupowanie dołączania i podzapytanie linq. Wyniki są równoważne.

class MyView
{
public integer Id {get,set};
    public String Field1  {get;set;}
public String Field2 {get;set;}
    public String SubQueryName {get;set;}                           
}

IList<MyView> list = await (from ci in _dbContext.Table1
                                               join cii in _dbContext.Table2
                                                   on ci.Id equals cii.Id

                                               where ci.Field1 == criterion
                                               group new
                                               {
                                                   ci.Id
                                               } by new { ci.Id, cii.Field1, ci.Field2}

                                           into pg
                                               select new MyView
                                               {
                                                   Id = pg.Key.Id,
                                                   Field1 = pg.Key.Field1,
                                                   Field2 = pg.Key.Field2,
                                                   SubQueryName=
                                                   (from chv in _dbContext.Table3 where chv.Id==pg.Key.Id select chv.Field1).FirstOrDefault()
                                               }).ToListAsync<MyView>();


 Compared to using a Left Join and Group new

IList<MyView> list = await (from ci in _dbContext.Table1
                                               join cii in _dbContext.Table2
                                                   on ci.Id equals cii.Id

                       join chv in _dbContext.Table3
                                                  on cii.Id equals chv.Id into lf_chv
                                                from chv in lf_chv.DefaultIfEmpty()

                                               where ci.Field1 == criterion
                                               group new
                                               {
                                                   ci.Id
                                               } by new { ci.Id, cii.Field1, ci.Field2, chv.FieldValue}

                                           into pg
                                               select new MyView
                                               {
                                                   Id = pg.Key.Id,
                                                   Field1 = pg.Key.Field1,
                                                   Field2 = pg.Key.Field2,
                                                   SubQueryName=pg.Key.FieldValue
                                               }).ToListAsync<MyView>();

-1
(from a in db.Assignments
     join b in db.Deliveryboys on a.AssignTo equals b.EmployeeId  

     //from d in eGroup.DefaultIfEmpty()
     join  c in  db.Deliveryboys on a.DeliverTo equals c.EmployeeId into eGroup2
     from e in eGroup2.DefaultIfEmpty()
     where (a.Collected == false)
     select new
     {
         OrderId = a.OrderId,
         DeliveryBoyID = a.AssignTo,
         AssignedBoyName = b.Name,
         Assigndate = a.Assigndate,
         Collected = a.Collected,
         CollectedDate = a.CollectedDate,
         CollectionBagNo = a.CollectionBagNo,
         DeliverTo = e == null ? "Null" : e.Name,
         DeliverDate = a.DeliverDate,
         DeliverBagNo = a.DeliverBagNo,
         Delivered = a.Delivered

     });

-1

Proste rozwiązanie dla LEFT OUTER JOIN :

var setA = context.SetA;
var setB = context.SetB.Select(st=>st.Id).Distinct().ToList();
var leftOuter  = setA.Where(stA=> !setB.Contains(stA.Id)); 

uwagi :

  • Aby poprawić wydajność, SetB można przekonwertować na Słownik (jeśli to zrobisz, musisz to zmienić :! SetB.Contains (stA.Id) ) lub HashSet
  • Gdy w grę wchodzi więcej niż jedno pole, można to osiągnąć za pomocą operacji Set i klasy, która implementuje: IEqualityComparer

Lewe sprzężenie zewnętrzne zwróci dopasowanie setAi setBodpowiedź.
NetMage
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.