我如何在Linq中执行与coalesce的左连接?

时间:2017-09-28 05:25:46

标签: linq left-join coalesce

考虑表

  • 发明,发明清单
  • 组件,可用于发明的所有组件的列表,以及
  • 发明组件,发明中使用的组分列表,具有计数

对于给定的发明,& InventionID,我想对所有组件进行“覆盖”左连接,而不仅仅是使用的组件。

SQL就像

select 
  I.name as inventionName
, C.name as componentName
, coalese (IC.count, 0) as componentCount
from
  (select &inventionID as inventionID, ID, name from components) C -- all components applied to some &inventionID
left join
  inventionComponents IC
on
  C.ID = IC.ComponentID 
  and C.inventionID = IC.inventionID
join
  inventions I
on
  I.ID = C.inventionID

https://dotnetfiddle.net/cx4bHp中.NET示例中的示例数据和Linq查询导致异常

[System.NullReferenceException: Object reference not set to an instance of an object.]

问题:如何修改Linq查询以执行所需的覆盖查询?

为完整起见,此处重复C#小提琴代码

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
  public static void Main()
  {
    var components = new List<Component>{
      new Component { ID=1, Name = "Florgebit" },
      new Component { ID=2, Name = "Phadron" },
      new Component { ID=3, Name = "Goobstem" },
      new Component { ID=4, Name = "Larchwren" },
      new Component { ID=5, Name = "Zangponder" },
      new Component { ID=6, Name = "Spoofork" },
      new Component { ID=7, Name = "Forkoon" },
      new Component { ID=8, Name = "Blidget" },
      new Component { ID=9, Name = "Wazzawim" },
      new Component { ID=10, Name = "Klackberg" },
    };
    var inventions = new List<Invention>{
      new Invention { ID=21, Name = "Swazzlute" },
      new Invention { ID=22, Name = "Corpocran" },
      new Invention { ID=23, Name = "Fillyboof" },
    };
    var inventionComponents  = new List<InventionComponent>{
      new InventionComponent { ID=100, InventionID=21, ComponentID=1, Count=1 },
      new InventionComponent { ID=101, InventionID=21, ComponentID=2, Count=2 },
      new InventionComponent { ID=102, InventionID=21, ComponentID=8, Count=3 },
      new InventionComponent { ID=103, InventionID=23, ComponentID=5, Count=4 },
      new InventionComponent { ID=104, InventionID=23, ComponentID=6, Count=5 },
      new InventionComponent { ID=105, InventionID=23, ComponentID=3, Count=4 },
      new InventionComponent { ID=106, InventionID=21, ComponentID=4, Count=3 },
      new InventionComponent { ID=107, InventionID=22, ComponentID=5, Count=2 },
      new InventionComponent { ID=108, InventionID=22, ComponentID=4, Count=1 },
      new InventionComponent { ID=109, InventionID=22, ComponentID=1, Count=6 },
      new InventionComponent { ID=110, InventionID=22, ComponentID=7, Count=1 },
      new InventionComponent { ID=111, InventionID=21, ComponentID=9, Count=1 },
    };

    var details =
      from A in inventions
      join B in inventionComponents on A.ID equals B.InventionID
      join C in components on B.ComponentID equals C.ID
      orderby A.Name, C.Name
      select new {
        InventionName = A.Name,
        ComponentName = C.Name,
        ComponentCount = B.Count
      };
/*
    foreach(var d in details)
    {
      Console.WriteLine("Invention: {0}, Component: {1}, Count: {2}", d.InventionName, d.ComponentName, d.ComponentCount);
    }
*/
    var inventionID = 22;
    var index = 1;

    // want full coverage of inventionID, componentID with applied counts
    // 22,1,6
    // 22,2,**0**
    // 22,3,**0**
    // 22,4,1
    // 22,5,2
    // 22,6,**0**
    // 22,7,1
    // 22,8,**0**
    // 22,9,**0**
    // 22,10,**0**

    var corpcheck = 
      from C in components select new { InventionID = inventionID, ComponentID = C.ID, ComponentName = C.Name } into allcomps
      join B in inventionComponents on new { allcomps.InventionID, allcomps.ComponentID } equals new { B.InventionID, B.ComponentID } into join1
      // from j1 in Join1 // inner join
      from j1 in join1.DefaultIfEmpty() // causes exception
      orderby allcomps.ComponentName
      select new {
          RowNum = index++,
        InventionID = allcomps.InventionID,
        ComponentName = allcomps.ComponentName,
        ComponentCount = j1.Count,
      };


    foreach(var x in corpcheck)
    {
      Console.WriteLine("InventionID: {0}, RowNum: {1}, ComponentName: {2}, Count: {3}", x.InventionID, x.RowNum, x.ComponentName, x.ComponentCount);
    }
  }

  public class Invention
  {
    public int ID { get; set; }
    public string Name { get; set; }
  }

  public class InventionComponent
  {
    public int ID { get; set; }
    public int InventionID { get; set; }
    public int ComponentID { get; set; }
    public int Count { get; set; }
  }

  public class Component
  {
    public int ID { get; set; }
    public string Name { get; set; }
  }
}

1 个答案:

答案 0 :(得分:2)

通过添加DefaultIfEmpty()j1对于某些组件可以为null。如果j1为空,我假设您希望count为0:

  from C in components select new { InventionID = inventionID, ComponentID = C.ID, ComponentName = C.Name } into allcomps
  join B in inventionComponents on new { allcomps.InventionID, allcomps.ComponentID } equals new { B.InventionID, B.ComponentID } into join1
  from j1 in join1.DefaultIfEmpty()
  orderby allcomps.ComponentName
  select new {
      RowNum = index++,
      InventionID = allcomps.InventionID,
      ComponentName = allcomps.ComponentName,
      ComponentCount = j1 == null ? 0 : j1.Count, // add null check
  };

在LINQ-to-objects中,您也可以使用ComponentCount = j1?.Count ?? 0。但我假设您将在LINQ中将其用于SQL后端。