LINQ中的LEFT OUTER JOIN

Toy*_*Toy 505 c# linq join

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

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})
Run Code Online (Sandbox Code Playgroud)

但对于左外连接我需要一个解决方案.我是这样的,但它不起作用

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
                                        })
Run Code Online (Sandbox Code Playgroud)

JoinPair是一个类:

public class JoinPair { long leftId; long rightId; }
Run Code Online (Sandbox Code Playgroud)

aja*_*hiz 568

如上所述:

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 };
Run Code Online (Sandbox Code Playgroud)

  • 我正在尝试相同的事情,但在连接运算符上得到一个错误,它说"连接子句中某个表达式的类型不正确". (7认同)
  • @jain如果您的类型不同,则连接将无效.因此,您的密钥可能具有不同的数据类型.两个键都是int例如吗? (3认同)
  • 现在我们可以使用空合并,如`select new { Category = c, ProductName = p.ProductName ?? “(没有产品)”};` (3认同)
  • 什么是解决方案耆那教?我也面临同样的错误,在我的情况下类型也是一样的. (2认同)
  • 更新链接:[101 LINQ示例 - 左外连接](https://code.msdn.microsoft.com/LINQ-Join-Operators-dabef4e9#leftouterjoin) (2认同)
  • 来自 ps.DefaultIfEmpty() 中的“p”,这个“p”与 products 中的 join p 中的“p”有什么关系吗? (2认同)
  • @user1169587 没有 (2认同)
  • Linq 中 lambda 的等价物是什么? (2认同)

Ste*_*ger 508

如果使用数据库驱动的LINQ提供程序,则可以如下编写更易读的左外连接:

from maintable in Repo.T_Whatever 
from xxx in Repo.T_ANY_TABLE.Where(join condition).DefaultIfEmpty()
Run Code Online (Sandbox Code Playgroud)

如果省略,DefaultIfEmpty()则会有内连接.

接受接受的答案:

  from c in categories
    join p in products on c equals p.Category into ps
    from p in ps.DefaultIfEmpty()
Run Code Online (Sandbox Code Playgroud)

这种语法非常混乱,当你想要离开连接MULTIPLE表时,它是如何工作的还不清楚.

注意
应该注意,from alias in Repo.whatever.Where(condition).DefaultIfEmpty()它与外部应用/左连接 - 横向相同,任何(正常的)数据库优化器完全能够转换为左连接,只要不引入每行-values(又名实际外部适用).不要在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();
Run Code Online (Sandbox Code Playgroud)

当与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
Run Code Online (Sandbox Code Playgroud)

编辑:

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

此外,如果你在Linq-2-Objects(而不是Linq-2-SQL)中进行,你应该采用老式的方式(因为LINQ to SQL正确地将其转换为连接操作,但是对象对象这个方法强制完全扫描,并没有利用索引搜索,为什么...):

    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
    }

);
Run Code Online (Sandbox Code Playgroud)

  • LINQ to SQL将此正确转换为连接操作.然而,这个方法强制完全扫描,这就是为什么官方文档提供了可以利用哈希来索引搜索的组连接解决方​​案. (30认同)
  • 这个答案实际上很有帮助.感谢您提供可理解的语法. (21认同)
  • WTB一个NHibernate兼容的LINQ查询...... :) (3认同)
  • 我认为显式`join`的语法比`where`后跟`DefaultIfEmpty更具可读性和清晰性 (3认同)
  • 这么多年左右加入另一种方式!谢谢! (2认同)

N R*_*ing 121

使用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   
  })
Run Code Online (Sandbox Code Playgroud)

  • 这一切都是为了执行左连接? (48认同)
  • Join和GroupJoin都不支持left-join.使用GroupJoin的诀窍是,您可以拥有空组,然后将这些空组转换为空值.DefaultIfEmpty只是这样做,这意味着`Enumerable.Empty <Product> .DefaultIfEmpty()`将返回一个IEnumerable,其值为`default(Product)`. (7认同)
  • 谢谢你!没有太多的lambda表达式示例,这对我有用. (6认同)
  • 真的不需要最后一个 Select(),SelectMany() 中的 anon obj 可以重构为相同的输出。另一个想法是测试 y 为 null 以模拟更接近的 LEFT JOIN 等价。 (5认同)
  • 正如 @DennyJacob 暗示的那样,您需要执行 `s.Product?.Name` 或以其他方式处理空值。令人困惑的是,如果左侧列表包含右侧列表中不包含的项目,则此页面上有多少答案将引发异常,即使处理该确切情况是左连接的全部目的。 (2认同)

Dev*_*art 40

看看这个例子.此查询应该有效:

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 };
Run Code Online (Sandbox Code Playgroud)

  • 使用连接后,可以在select子句中访问`r`吗? (3认同)
  • 从左到右到leftrights in right on left in leftRights... Oh jeez... 在LINQ *really* 中使用LEFT OUTER JOIN 的语法不清楚,但这些名称确实使它更加不清楚。 (2认同)

Mat*_*ius 39

现在作为扩展方法:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

使用就像你通常使用join一样:

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

希望这能为您节省一些时间.

  • 这不应该是“IQueryable”吗? (4认同)
  • 这很好,但在您的示例中,如果“list”包含键“list2”不包含键,“r.people”不会抛出异常吗,因为“r”将是“null”?不应该是“r?.people”吗?否则,这只是一个也会引发异常的内部联接。或者,我认为您可以向 `LeftOuterJoin()` 添加一个“默认右元素”参数,并将其传递到 `DefaultIfEmpty()` 中。 (2认同)

Ber*_*and 17

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

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));
     }
   }
Run Code Online (Sandbox Code Playgroud)

然后resultselector必须处理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));
   }
Run Code Online (Sandbox Code Playgroud)

  • 但问题是"如何在C#LINQ中执行左外连接到_objects_ ......" (13认同)
  • 但这只是*LINQ to对象的一个​​选项,并且无法将查询转换为任何查询提供程序,这是此操作的最常见用例. (4认同)

Bas*_*ANI 10

看看这个例子

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:
Run Code Online (Sandbox Code Playgroud)

现在你include elements from the left甚至可以使用那个元素has no matches in the right,在我们的例子中,Arlene即使他在右边没有匹配,我们也会重新开始

这是参考

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


Chr*_*row 9

这是一般形式(已在其他答案中提供)

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 };
Run Code Online (Sandbox Code Playgroud)

然而,这是一个解释,我希望将澄清这实际意味着什么!

join b in beta on b.field1 equals a.field1 into b_temp
Run Code Online (Sandbox Code Playgroud)

实际上创建了一个单独的结果集b_temp,它有效地包括右侧条目的空"行"("b"中的条目).

然后是下一行:

from b_value in b_temp.DefaultIfEmpty()
Run Code Online (Sandbox Code Playgroud)

..对结果集进行设置,在右侧设置'row'的默认空值,并将右侧行连接的结果设置为'b_value'的值(即右边的值)手边,如果有匹配的记录,或'null',如果没有).

现在,如果右侧是单独的LINQ查询的结果,它将由匿名类型组成,它们只能是"某事"或"空".如果它是一个可枚举的(例如一个List - 其中MyObjectB是一个有2个字段的类),那么就可以具体说明默认的'null'值用于它的属性:

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 };
Run Code Online (Sandbox Code Playgroud)

这确保'b'本身不为null(但是它的属性可以为null,使用您指定的默认空值),这允许您检查b_value的属性而不会获得b_value的空引用异常.请注意,对于可为空的DateTime,必须在"DefaultIfEmpty"的规范中将(DateTime?)类型(即"可空的DateTime")指定为null的"Type"(这也适用于非本机的类型) '可空,例如双,漂浮).

您只需链接上述语法即可执行多个左外连接.

  • b_value 从哪里来? (3认同)

Sam*_*eer 8

如果您需要加入2个以上的表,这是一个示例:

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()
Run Code Online (Sandbox Code Playgroud)

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


小智 5

扩展方法类似于使用 Join 语法的 left 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()));
    }
}
Run Code Online (Sandbox Code Playgroud)

刚刚在 .NET 核心中编写它,它似乎按预期工作。

小测试:

        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)}
Run Code Online (Sandbox Code Playgroud)


Ree*_*ind 5

我想补充一点,如果您获得 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
Run Code Online (Sandbox Code Playgroud)

编辑:

回想起来,这可能有效,但它会将 IQueryable 转换为 IEnumerable,因为 morelinq 不会将查询转换为 SQL。

您可以改为使用 GroupJoin,如下所述:https : //stackoverflow.com/a/24273804/4251433

这将确保它保持为 IQueryable,以防您稍后需要对其进行进一步的逻辑操作。


Tim*_*ann 5

这是一个使用方法语法的相当容易理解的版本:

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 }));
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

640807 次

最近记录:

6 年,9 月 前