简体   繁体   English

LINQ 中的左外连接

[英]LEFT OUTER JOIN in LINQ

How to perform left outer join in C# LINQ to objects without using join-on-equals-into clauses?如何在不使用join-on-equals-into子句的情况下对对象执行 C# LINQ 中的左外连接? Is there any way to do that with where clause?有没有办法用where子句做到这一点? Correct problem: For inner join is easy and I have a solution like this正确的问题:对于内部连接很容易,我有这样的解决方案

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

but for left outer join I need a solution.但对于左外连接,我需要一个解决方案。 Mine is something like this but it's not working我的是这样的,但它不工作

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

where JoinPair is a class:其中JoinPair是 class:

public class JoinPair { long leftId; long rightId; }

As stated in "Perform left outer joins" :“执行左外连接”中所述:

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

If a database driven LINQ provider is used, a significantly more readable left outer join can be written as such:如果使用数据库驱动的 LINQ 提供程序,则可以这样编写可读性更高的左外连接:

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

If you omit the DefaultIfEmpty() you will have an inner join.如果您省略DefaultIfEmpty()您将有一个内部连接。

Take the accepted answer:接受公认的答案:

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

This syntax is very confusing, and it's not clear how it works when you want to left join MULTIPLE tables.这种语法非常令人困惑,当您想要左连接 MULTIPLE 表时,不清楚它是如何工作的。

Note笔记
It should be noted that from alias in Repo.whatever.Where(condition).DefaultIfEmpty() is the same as an outer-apply/left-join-lateral, which any (decent) database-optimizer is perfectly capable of translating into a left join, as long as you don't introduce per-row-values (aka an actual outer apply).应该注意的是from alias in Repo.whatever.Where(condition).DefaultIfEmpty()与外部应用/左连接横向相同,任何(体面的)数据库优化器都完全能够将其转换为左连接,只要您不引入每行值(也就是实际的外部应用)。 Don't do this in Linq-2-Objects (because there's no DB-optimizer when you use Linq-to-Objects).不要在 Linq-2-Objects 中执行此操作(因为使用 Linq-to-Objects 时没有 DB-optimizer)。

Detailed Example详细示例

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

When used with LINQ 2 SQL it will translate nicely to the following very legible SQL query:当与 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

Edit:编辑:

See also " Convert SQL Server query to Linq query " for a more complex example.另请参阅“ 将 SQL Server 查询转换为 Linq 查询”以获得更复杂的示例。

Also, If you're doing it in Linq-2-Objects (instead of Linq-2-SQL), you should do it the old-fashioned way (because LINQ to SQL translates this correctly to join operations, but over objects this method forces a full scan, and doesn't take advantage of index searches, whyever...):此外,如果您在 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
    }

);

Using lambda expression使用 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   
  });

Now as an extension method:现在作为扩展方法:

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

Use like you would normally use join:像通常使用 join 一样使用:

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

Hope this saves you some time.希望这可以节省您一些时间。

Take a look at this example .看看这个例子 This query should work:此查询应该有效:

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

An implementation of left outer join by extension methods could look like通过扩展方法实现左外连接可能看起来像

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

The resultselector then has to take care of the null elements.然后结果选择器必须处理空元素。 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));
   }

take look at this example看看这个例子

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:

now you are able to include elements from the left even if that element has no matches in the right , in our case we retrived Arlene even he has no matching in the right现在您可以include elements from the left即使该元素has no matches in the right项,在我们的例子中,我们检索了Arlene ,即使他在右侧没有匹配项

here is the reference这是参考

How to: Perform Left Outer Joins (C# Programming Guide)如何:执行左外连接(C# 编程指南)

This is the general form (as already provided in other answers)这是一般形式(已在其他答案中提供)

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

However here's an explanation that I hope will clarify what this actually means!然而,这里有一个解释,我希望能澄清这实际上意味着什么!

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

essentially creates a separate result set b_temp that effectively includes null 'rows' for entries on the right hand side (entries in 'b').本质上创建了一个单独的结果集 b_temp,它有效地包括右侧条目的空“行”(“b”中的条目)。

Then the next line:然后是下一行:

from b_value in b_temp.DefaultIfEmpty()

..iterates over that result set, setting the default null value for the 'row' on the right hand side, and setting the result of the right hand side row join to the value of 'b_value' (ie the value that's on the right hand side,if there's a matching record, or 'null' if there isn't). ..迭代该结果集,为右侧的“行”设置默认空值,并将右侧行连接的结果设置为“b_value”的值(即右侧的值手边,如果有匹配的记录,如果没有,则为“null”)。

Now, if the right hand side is the result of a separate LINQ query, it will consist of anonymous types, which can only either be 'something' or 'null'.现在,如果右侧是单独的 LINQ 查询的结果,它将由匿名类型组成,只能是“某物”或“空”。 If it's an enumerable however (eg a List - where MyObjectB is a class with 2 fields), then it's possible to be specific about what default 'null' values are used for its properties:但是,如果它是可枚举的(例如 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 };

This ensures that 'b' itself isn't null (but its properties can be null, using the default null values that you've specified), and this allows you to check properties of b_value without getting a null reference exception for b_value.这确保了 'b' 本身不为空(但它的属性可以为空,使用您指定的默认空值),这允许您检查 b_value 的属性,而不会获得 b_value 的空引用异常。 Note that for a nullable DateTime, a type of (DateTime?) ie 'nullable DateTime' must be specified as the 'Type' of the null in the specification for the 'DefaultIfEmpty' (this will also apply to types that are not 'natively' nullable eg double, float).请注意,对于可为空的 DateTime,(DateTime?)类型,即“可为空的 DateTime”,必须在“DefaultIfEmpty”的规范中指定为 null 的“类型”(这也适用于不是“本机”的类型' 可以为空,例如 double、float)。

You can perform multiple left outer joins by simply chaining the above syntax.您可以通过简单地链接上述语法来执行多个左外连接。

Here's an example if you need to join more than 2 tables:如果您需要加入 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()

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

Here is a fairly easy to understand version using method syntax:这是一个使用方法语法的相当容易理解的版本:

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

Extension method that works like left join with Join syntax扩展方法,类似于使用 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()));
    }
}

just wrote it in .NET core and it seems to be working as expected.刚刚在 .NET 核心中编写它,它似乎按预期工作。

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

I would like to add that if you get the MoreLinq extension there is now support for both homogenous and heterogeneous left joins now我想补充一点,如果您获得了 MoreLinq 扩展,现在现在支持同质和异构左连接

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

example:例子:

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

EDIT:编辑:

In retrospect this may work, but it converts the IQueryable to an IEnumerable as morelinq does not convert the query to SQL.回想起来,这可能有效,但它将 IQueryable 转换为 IEnumerable,因为 morelinq 不会将查询转换为 SQL。

You can instead use a GroupJoin as described here: https://stackoverflow.com/a/24273804/4251433您可以改为使用 GroupJoin,如下所述: https ://stackoverflow.com/a/24273804/4251433

This will ensure that it stays as an IQueryable in case you need to do further logical operations on it later.这将确保它保持为 IQueryable,以防您稍后需要对其进行进一步的逻辑操作。

There are three tables: persons, schools and persons_schools, which connects persons to the schools they study in. A reference to the person with id=6 is absent in the table persons_schools.共有三个表:persons、schools 和 persons_schools,将人员与他们就读的学校联系起来。persons_schools 表中没有对 id=6 的人员的引用。 However the person with id=6 is presented in the result lef-joined grid.然而,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);
}

Easy way is to use Let keyword.简单的方法是使用 Let 关键字。 This works for me.这对我有用。

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
}

This is a simulation of Left Join.这是Left Join的模拟。 If each item in B table not match to A item , BItem return null如果 B 表中的每个项目都与 A 项目不匹配,则 BItem 返回 null

This is a SQL syntax compare to LINQ syntax for inner and left outer joins.这是一种 SQL 语法,与用于内连接和左外连接的 LINQ 语法相比。 Left Outer Join:左外连接:

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

"The following example does a group join between product and category. This is essentially the left join. The into expression returns data even if the category table is empty. To access the properties of the category table, we must now select from the enumerable result by adding the from cl in catList.DefaultIfEmpty() statement. “下面的例子在产品和类别之间进行组连接。这本质上是左连接。即使类别表为空,into 表达式也会返回数据。要访问类别表的属性,我们现在必须从可枚举的结果中进行选择通过在 catList.DefaultIfEmpty() 语句中添加 from cl。

As per my answer to a similar question, here:根据我对类似问题的回答,在这里:

Linq to SQL left outer join using Lambda syntax and joining on 2 columns (composite join key) Linq to SQL 使用 Lambda 语法左外连接并连接 2 列(复合连接键)

Get the code here , or clone my github repo , and play!这里获取代码,或者克隆我的 github 仓库,然后玩!

Query:询问:

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

Perform left outer joins in linq C# // Perform left outer joins在 linq C# 中执行左外连接 // 执行左外连接

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/ https://dotnetwithhamid.blogspot.in/

Here's a version of the extension method solution using IQueryable instead of IEnumerable这是使用 IQueryable 而不是 IEnumerable 的扩展方法解决方案的一个版本

public class OuterJoinResult<TLeft, TRight>
{
    public TLeft LeftValue { get; set; }
    public TRight RightValue { get; set; }
}

public static IQueryable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(this IQueryable<TLeft> left, IQueryable<TRight> right, Expression<Func<TLeft, TKey>> leftKey, Expression<Func<TRight, TKey>> rightKey, Expression<Func<OuterJoinResult<TLeft, TRight>, TResult>> result)
{
    return left.GroupJoin(right, leftKey, rightKey, (l, r) => new { l, r })
          .SelectMany(o => o.r.DefaultIfEmpty(), (l, r) => new OuterJoinResult<TLeft, TRight> { LeftValue = l.l, RightValue = r })
          .Select(result);
}

If you need to join and filter on something, that can be done outside of the join.如果您需要加入并过滤某些内容,可以在加入之外完成。 Filter can be done after creating the collection.可以在创建集合后进行过滤。

In this case if I do this in the join condition I reduce the rows that are returned.在这种情况下,如果我在连接条件中执行此操作,我会减少返回的行。

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

  • If the object is null (so no match), then return what is after the ?如果对象为null (因此不匹配),则返回 ? 之后的内容? . . __ , in this case. __ ,在这种情况下。

  • Else, return what is after the : , n.MonDayNote .否则,返回:n.MonDayNote之后的内容。

Thanks to the other contributors that is where I started with my own issue.感谢其他贡献者,这是我从自己的问题开始的地方。


        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);
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输出

Overview: In this code snippet, I demonstrate how to group by ID where Table1 and Table2 have a one to many relationship.概述:在此代码段中,我演示了如何在 Table1 和 Table2 具有一对多关系的情况下按 ID 进行分组。 I group on Id, Field1, and Field2.我对 Id、Field1 和 Field2 进行分组。 The subquery is helpful, if a third Table lookup is required and it would have required a left join relationship.如果需要第三次表查找并且它需要左连接关系,则子查询很有帮助。 I show a left join grouping and a subquery linq.我展示了一个左连接分组和一个子查询 linq。 The results are equivalent.结果是等价的。

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

The prescribed way to do a left join using LINQ is to to a GroupJoin followed by a SelectMany .使用 LINQ 进行左连接的规定方法是到GroupJoin后跟SelectMany I prefer to abstract this logic into a LeftJoin extension method to make the code much more readable.我更喜欢将此逻辑抽象为LeftJoin扩展方法,以使代码更具可读性。

Here is the LeftJoin implementation that I use.这是我使用的LeftJoin实现。 The two major benefits of this implementation are:此实施的两个主要好处是:

  1. It specifically works with IQueryable s它特别适用于IQueryable s

  2. It has the exact same parameter and return type as the Join method:它具有与Join方法完全相同的参数和返回类型:

     var results = DbContext.Categories.LeftJoin( DbContext.Products, c => c.Id, p => p.CategoryId, (c, p) => new { Category = c, ProductName = p == null? "(No Products)": p.ProductName }).ToList(); var results = DbContext.Categories.Join( DbContext.Products, c => c.Id, p => p.CategoryId, (c, p) => new { Category = c, ProductName = p.ProductName }).ToList();

Here's how:就是这样:

IQueryable Extensions: IQueryable 扩展:

public static class QueryableExtensions
{
    public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(this IQueryable<TOuter> outer, IEnumerable<TInner> inner, Expression<Func<TOuter, TKey>> outerKeySelector, Expression<Func<TInner, TKey>> innerKeySelector, Expression<Func<TOuter, TInner, TResult>> resultSelector)
    {
        var query = outer
            .GroupJoin(inner, outerKeySelector, innerKeySelector, (o, i) => new { o, i })
            .SelectMany(o => o.i.DefaultIfEmpty(), (x, i) => new { x.o, i });
        return ApplySelector(query, x => x.o, x => x.i, resultSelector);
    }

    private static IQueryable<TResult> ApplySelector<TSource, TOuter, TInner, TResult>(
        IQueryable<TSource> source,
        Expression<Func<TSource, TOuter>> outerProperty,
        Expression<Func<TSource, TInner>> innerProperty,
        Expression<Func<TOuter, TInner, TResult>> resultSelector)
    {
        var p = Expression.Parameter(typeof(TSource), $"param_{Guid.NewGuid()}".Replace("-", string.Empty));
        Expression body = resultSelector?.Body
            .ReplaceParameter(resultSelector.Parameters[0], outerProperty.Body.ReplaceParameter(outerProperty.Parameters[0], p))
            .ReplaceParameter(resultSelector.Parameters[1], innerProperty.Body.ReplaceParameter(innerProperty.Parameters[0], p));
        var selector = Expression.Lambda<Func<TSource, TResult>>(body, p);
        return source.Select(selector);
    }
}

Expression Extensions:表达式扩展:

public static class ExpressionExtensions
{
    public static Expression ReplaceParameter(this Expression source, ParameterExpression toReplace, Expression newExpression)
        => new ReplaceParameterExpressionVisitor(toReplace, newExpression).Visit(source);
}

Expression Visitors:表达访客:

public class ReplaceParameterExpressionVisitor : ExpressionVisitor
{
    public ReplaceParameterExpressionVisitor(ParameterExpression toReplace, Expression replacement)
    {
        this.ToReplace = toReplace;
        this.Replacement = replacement;
    }

    public ParameterExpression ToReplace { get; }

    public Expression Replacement { get; }

    protected override Expression VisitParameter(ParameterExpression node)
        => (node == ToReplace) ? Replacement : base.VisitParameter(node);
}

This is the prettiest solution I use, give it a try!这是我使用的最漂亮的解决方案,试一试!

(from c in categories
      let product = products.Where(d=> d.Category == c.Category).FirstOrDefault()
    select new { Category = c, ProductName = p == null ? "(No products)" : product.ProductName };
(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

     });

Simple solution for the LEFT OUTER JOIN : 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)); 

notes :笔记

  • To improve performance SetB could be converted to a Dictionary (if that is done then you have to change this: !setB.Contains(stA.Id) ) or a HashSet为了提高性能 SetB 可以转换为字典(如果这样做,那么你必须改变这个: !setB.Contains(stA.Id) )或一个HashSet
  • When there is more than one field involved this could be achieve using Set operations and a class that implement: IEqualityComparer当涉及多个字段时,这可以使用Set操作和实现的类来实现: IEqualityComparer

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM