LEFT OUTER JOIN in LINQ

Come eseguire il join esterno sinistro in C # LINQ agli oggetti senza utilizzare clausole join-on-equals-into ? C’è un modo per farlo con la clausola where? Problema corretto: per inner join è facile e ho una soluzione come questa

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

ma per il join esterno a sinistra ho bisogno di una soluzione. Il mio è qualcosa del genere ma non funziona

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

dove JoinPair è una class:

 public class JoinPair { long leftId; long rightId; } 

Come affermato su:

101 Esempi LINQ: join esterno sinistro

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

Necromancing.
Se viene utilizzato un provider LINQ basato su database, è ansible scrivere un join esterno sinistro significativamente più leggibile come tale:

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

Se si omette DefaultIfEmpty() si avrà un inner join.

Rispondi alla risposta accettata:

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

Questa syntax è molto confusa e non è chiaro come funzioni quando vuoi lasciare unire tabelle MULTIPLE.

Nota
Va notato che from alias in Repo.whatever.Where(condition).DefaultIfEmpty() è lo stesso di outer-apply / left-join-lateral, che qualsiasi ottimizzatore di database (non ritardato) è perfettamente in grado di tradurre in un join di sinistra, a condizione che non vengano introdotti valori per riga (ovvero un’effettiva applicazione esterna). Non farlo in Linq-2-Objects (perché non c’è DB-optimizer quando si usa Linq-to-Objects).

Esempio dettagliato

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

Se utilizzato con LINQ 2 SQL, si tradurrà in modo gradevole alla seguente query SQL molto leggibile:

 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 

Modificare:

Vedi anche " Converti query SQL Server in query Linq " per un esempio più complesso.

Inoltre, se lo stai facendo in Linq-2-Objects (invece di Linq-2-SQL), dovresti farlo alla vecchia maniera (perché LINQ to SQL lo traduce correttamente per unire le operazioni, ma sugli oggetti questo metodo forza una scansione completa e non sfrutta le ricerche di indice, perché ...):

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

Usando l’espressione 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 }) 

Dai un’occhiata a questo esempio . Questa query dovrebbe funzionare:

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

Ora come metodo di estensione:

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

Utilizzare come si usa normalmente join:

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

Spero che questo ti salvi un po ‘di tempo.

Potrebbe essere simile a un’implementazione del join esterno sinistro mediante metodi di estensione

 public static IEnumerable LeftJoin( this IEnumerable outer, IEnumerable inner , Func outerKeySelector, Func innerKeySelector , Func resultSelector, IEqualityComparer 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.Default); } static IEnumerable LeftJoinImpl( IEnumerable outer, IEnumerable inner , Func outerKeySelector, Func innerKeySelector , Func resultSelector, IEqualityComparer 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)); } } 

Il resultselector deve quindi occuparsi degli elementi null. 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)); } 

Questa è la forma generale (come già fornita in altre risposte)

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

Comunque ecco una spiegazione che spero chiarirà cosa significa in realtà!

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

essenzialmente crea un set di risultati separato b_temp che include effettivamente “righe” nulle per le voci sul lato destro (voci in “b”).

Quindi la riga successiva:

 from b_value in b_temp.DefaultIfEmpty() 

..propone il set di risultati, impostando il valore null predefinito per la ‘riga’ sul lato destro e impostando il risultato del join della riga sul lato destro sul valore di ‘b_value’ (cioè il valore che si trova sulla destra lato mano, se c’è un record corrispondente, o ‘null’ se non c’è).

Ora, se il lato destro è il risultato di una query LINQ separata, sarà costituita da tipi anonimi, che possono essere solo “qualcosa” o “nulla”. Tuttavia, se è un enumerabile (ad esempio un elenco – in cui MyObjectB è una class con 2 campi), è ansible essere specifici su quali valori “nulli” predefiniti vengono utilizzati per le sue proprietà:

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

Ciò garantisce che ‘b’ stesso non sia null (ma le sue proprietà possono essere nulle, usando i valori null di default che hai specificato), e questo ti permette di controllare le proprietà di b_value senza ottenere un’eccezione di riferimento null per b_value. Si noti che per un valore DateTime nullable, un tipo di (DateTime?) Vale a dire ‘nullable DateTime’ deve essere specificato come ‘Tipo’ del null nella specifica per ‘DefaultIfEmpty’ (questo si applica anche ai tipi che non sono ‘nativamente ‘nullable eg double, float).

È ansible eseguire più join esterni a sinistra semplicemente concatenando la syntax precedente.

Ecco un esempio se devi unirti a più di 2 tabelle:

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

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

dare un’occhiata a questo esempio

 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 people = new List {magnus, terry, charlotte, arlene}; List pets = new List {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: 

ora puoi include elements from the left anche se quell’elemento has no matches in the right , nel nostro caso abbiamo recuperato Arlene anche se non ha corrispondenze nella destra

ecco il riferimento

Procedura: eseguire un join esterno sinistro (Guida di programmazione C #)

Ci sono tre tabelle: persone, scuole e persone_scuole, che collegano le persone alle scuole in cui studiano. Un riferimento alla persona con id = 6 è assente nella tabella persone_schools. Tuttavia la persona con id = 6 viene presentata nella griglia unita a lef risultato.

 List persons = new List { 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 schools = new List { 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 persons_schools = new List { 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); } 

Questa è una syntax SQL confrontata con la syntax LINQ per i join esterni interni e esterni. Left Outer Join:

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

“L’esempio seguente fa un join di gruppo tra prodotto e categoria, che è essenzialmente il join di sinistra: l’espressione in restituisce i dati anche se la tabella delle categorie è vuota. Per accedere alle proprietà della tabella delle categorie, dobbiamo ora selezionare dal risultato enumerabile aggiungendo l’istruzione from cl in catList.DefaultIfEmpty ().

Esegue join esterni a sinistra in linq C # // Esegue join esterni a sinistra

 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 people = new List { magnus, terry, charlotte, arlene }; List childs = new List { 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/

Se è necessario unirsi e filtrare su qualcosa, è ansible farlo al di fuori del join. Il filtro può essere fatto dopo aver creato la collezione.

In questo caso, se eseguo questa operazione nella condizione di join, riduco le righe restituite.

Viene utilizzata la condizione (= n == null ? "__" : n.MonDayNote,)

  • Se l’object è null (quindi nessuna corrispondenza), quindi restituisce ciò che è dopo il ? . __ , in questo caso.

  • Altrimenti, restituire ciò che è dopo n.MonDayNote .

Grazie agli altri contributori che è dove ho iniziato con il mio problema.


  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 LambdaManning = (x) => { return x == 0 ? "" : "Manning:" + x.ToString(); }; DataTable dt_ScheduleMaster = PsuedoSchedule.Tables["ScheduleMasterWithNotes"]; var schedLocations2 = schedLocations.Where(x => x.SchedMondayDate == dMon); 

Il metodo di estensione che funziona come sinistra si unisce alla syntax di Join

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

l’ho appena scritto in .NET core e sembra che funzioni come previsto.

Piccolo test:

  var Ids = new List { 1, 2, 3, 4}; var items = new List> { new Tuple(1,"a"), new Tuple(2,"b"), new Tuple(4,"d"), new Tuple(5,"e"), }; var result = Ids.LeftJoin( items, id => id, item => item.Item1, (id, item) => item ?? new Tuple(id, "not found")); result.ToList() Count = 4 [0]: {(1, a)} [1]: {(2, b)} [2]: {(3, not found)} [3]: {(4, d)} 

Ecco una versione abbastanza facile da capire usando la syntax del metodo:

 IEnumerable outerLeft = lefts.SelectMany(l => rights.Where(r => l.Key == r.Key) .DefaultIfEmpty(new Item()) .Select(r => new JoinPair { LeftId = l.Id, RightId = r.Id })); 
 (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 }); 
 class Program { List listOfEmp = new List(); List listOfDepart = new List(); public Program() { listOfDepart = new List(){ 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(){ 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; } } 

PRODUZIONE

Soluzione semplice per il 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)); 

note :

  • Per migliorare le prestazioni, SetB potrebbe essere convertito in un dizionario (se ciò avviene, devi cambiare questo :! SetB.Contains (stA.Id) ) o un hashset
  • Quando è coinvolto più di un campo, ciò può essere ottenuto utilizzando le operazioni Set e una class che implementa: IEqualityComparer

Vorrei aggiungere che se si ottiene l’estensione MoreLinq ora è disponibile il supporto per i join di sinistra sia omogenei che eterogenei

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

esempio:

 //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 

MODIFICARE:

In retrospettiva, questo può funzionare, ma converte l’IQueryable in un object IEnumerable poiché morelinq non converte la query in SQL.

Puoi invece utilizzare un GroupJoin come descritto qui: https://stackoverflow.com/a/24273804/4251433

Ciò garantirà che rimanga come IQueryable nel caso in cui sia necessario eseguire ulteriori operazioni logiche su di esso in un secondo momento.