LEFT OUTER JOIN LINQ

如何在不使用join-on-equals-into子句的情况下将C#LINQ中的左外连接执行到对象? 有什么方法可以用where子句做到这一点? 正确的问题:内部连接很容易,我有这样的解决scheme

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

但是对于左外连接,我需要一个解决scheme。 我的是这样的,但它不工作

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

JoinPair是一个类:

 public class JoinPair { long leftId; long rightId; } 

如上所述:

101 LINQ示例 – 左外连接

 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。
如果使用数据库驱动的LINQ提供程序,则可以这样编写可读性更高的左外部联接:

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

如果你省略了DefaultIfEmpty()你将会有一个内部连接。

采取接受的答案:

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

这个语法非常混乱,当你想要连接MULTIPLE表时,它的工作原理并不清楚。

注意
值得注意的是, from alias in Repo.whatever.Where(condition).DefaultIfEmpty()中的from alias in Repo.whatever.Where(condition).DefaultIfEmpty()可以看出,与任何(非延迟的)数据库优化器完全能够翻译的外应用/进入一个左连接,只要你不引入每行值(又名实际的外应用)。 不要在Linq-2-Objects中执行此操作(因为在使用Linq-to-Objects时没有DB优化器)。

详细的例子

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

当与LINQ 2 SQL一起使用时,它会很好地转换成以下非常清晰的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 

编辑:

有关更复杂的示例,另请参阅“ 将SQL Server查询转换为Linq查询 ”。

另外,如果你在Linq-2-Objects(而不是Linq-2-SQL)中这样做,你应该用老式的方式来做(因为LINQ to SQL正确地把这个转换成连接操作,强制全面扫描,并没有利用索引search,为什么…):

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

使用lambdaexpression式

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

看看这个例子 。 这个查询应该工作:

 var leftFinal = from l in lefts join r in rights on l equals r.Left into lrs from lr in lrs.DefaultIfEmpty() select new { LeftId = l.Id, RightId = ((l.Key==r.Key) ? r.Id : 0 }; 

通过扩展方法实现左外连接可能看起来像

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

结果select器则必须处理空元素。 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)); } 

现在作为扩展方法:

 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 => orDefaultIfEmpty(), (l, r) => new { lft= ll, rght = r }) .Select(o => result.Invoke(o.lft, o.rght)); } } 

像你通常使用连接一样使用:

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

希望这可以为你节省一些时间。

下面是一个例子,如果你需要join两个以上的表格:

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

参考: https : //stackoverflow.com/a/17142392/2343

有三张表格:个人,学校和人员学校,将人员连接到他们学习的学校。对于id = 6的人员的提及在表格人员学校中不存在。 然而,id = 6的人在结果连接的网格中出现。

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

看看这个例子

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

这里是参考

如何执行左外连接(C#编程指南)

这是一般forms(正如在其他答案中已经提供的那样)

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

不过这里有一个解释,我希望能澄清这实际上是什么意思!

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

基本上创build了一个单独的结果集b_temp,它有效地包含右侧条目的空行(“b”中的条目)。

然后下一行:

 from b_value in b_temp.DefaultIfEmpty() 

..iteite在结果集上,为右侧的'row'设置默认的空值,并将右侧的row join的结果设置为'b_value'的值(即右侧的值如果有匹配的logging,则为手边,如果没有,则为“无”)。

现在,如果右侧是单独的LINQ查询的结果,它将由匿名types组成,它们只能是“某事”或“空”。 如果它是一个枚举然而(例如一个列表 – 其中MyObjectB是一个具有2个字段的类),那么可以具体说明哪些默认的“空值”用于其属性:

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

这可以确保“b”本身不为空(但是它的属性可以为空,使用您指定的默认空值),这可以检查b_value的属性,而不会为b_value获取空引用exception。 请注意,对于可为空的DateTime,必须将(DateTime?)types(即“可空DateTime”)指定为“DefaultIfEmpty”的规范中的“types”(这也适用于不是“本地'可空,例如double,float)。

您可以通过链接上面的语法来执行多个左外连接。

这是一个SQL语法,与内部和外部连接的LINQ语法比较。 左外连接:

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

“下面的例子在产品和类别之间进行了一个组连接,这实质上是左连接,即使类别表是空的,expression式也会返回数据,为了访问类别表的属性,我们现在必须从可枚举的结果在catList.DefaultIfEmpty()语句中添加from cl。

如果您需要join并过滤某些内容,则可以在join之外完成。 filter可以在创build集合之后完成。

在这种情况下,如果我在连接条件中这样做,我减less返回的行。

使用三元条件(= n == null ? "__" : n.MonDayNote,)

  • 如果对象为null (所以不匹配),那么返回什么?__ ,在这种情况下。

  • 否则,返回之后是:n.MonDayNote

感谢其他的贡献者,这是我从自己的问题开始的地方。


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

OUTPUT

简单的解决scheme为左外联接

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

笔记

  • 为了提高性能,SetB可以被转换成一个Dictionary (如果这样做,那么你必须改变这个: !setB.Contains(stA.Id) )或者一个HashSet
  • 当涉及多个字段时,可以使用Set操作和实现以下内容的类来实现: IEqualityComparer

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

只是写在.NET核心,它似乎按预期工作。

小testing:

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

我想补充一点,如果你得到MoreLinq扩展,现在支持同时和异构的左连接

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

例:

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