如何在Linq中使用coalesce执行左连接?

如何在Linq中使用coalesce执行左连接?,linq,left-join,coalesce,Linq,Left Join,Coalesce,以桌子为例 发明,发明清单 组件,可用于发明的所有组件列表,以及 发明组件,发明中使用组件的列表,带计数 对于给定的发明&发明ID,我希望对所有组件进行“覆盖”左连接,而不仅仅是使用的组件 SQL将类似于 select I.name as inventionName , C.name as componentName , coalese (IC.count, 0) as componentCount from (select &inventionID as inventio

以桌子为例

  • 发明,发明清单
  • 组件,可用于发明的所有组件列表,以及
  • 发明组件,发明中使用组件的列表,带计数
对于给定的发明&发明ID,我希望对所有组件进行“覆盖”左连接,而不仅仅是使用的组件

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
NET FIDLE at中的示例数据和Linq查询导致异常

[System.NullReferenceException: Object reference not set to an instance of an object.]
问题:应该如何修改Linq查询以执行所需的覆盖查询

为了完整起见,这里重复了C#fiddle代码

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; }
  }
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
公共课程
{
公共静态void Main()
{
var组件=新列表{
新组件{ID=1,Name=“Florgebit”},
新组件{ID=2,Name=“Phadron”},
新组件{ID=3,Name=“Goobstem”},
新组件{ID=4,Name=“Larchwren”},
新组件{ID=5,Name=“zangpound”},
新组件{ID=6,Name=“Spoofork”},
新组件{ID=7,Name=“Forkoon”},
新组件{ID=8,Name=“Blidget”},
新组件{ID=9,Name=“Wazzawim”},
新组件{ID=10,Name=“Klackberg”},
};
var=新列表{
新发明{ID=21,Name=“swazzute”},
新发明{ID=22,Name=“Corpocran”},
新发明{ID=23,Name=“Fillyboof”},
};
var inventionComponents=新列表{
新发明组件{ID=100,发明ID=21,组件ID=1,计数=1},
新发明组件{ID=101,发明ID=21,组件ID=2,计数=2},
新发明组件{ID=102,发明ID=21,组件ID=8,计数=3},
新发明组件{ID=103,发明ID=23,组件ID=5,计数=4},
新发明组件{ID=104,发明ID=23,组件ID=6,计数=5},
新发明组件{ID=105,发明ID=23,组件ID=3,计数=4},
新发明组件{ID=106,发明ID=21,组件ID=4,计数=3},
新发明组件{ID=107,发明ID=22,组件ID=5,计数=2},
新发明组件{ID=108,发明ID=22,组件ID=4,计数=1},
新发明组件{ID=109,发明ID=22,组件ID=1,计数=6},
新发明组件{ID=110,发明ID=22,组件ID=7,计数=1},
新发明组件{ID=111,发明ID=21,组件ID=9,计数=1},
};
var详细信息=
从发明的角度来看
将B加入A.ID上的发明组件等于B.InnovationID
将C连接到B上的组件中。ComponentID等于C.ID
按A.名称、C.名称排序
选择新的{
发明名称=A.名称,
ComponentName=C.名称,
组件计数=B.计数
};
/*
foreach(详细信息为var d)
{
WriteLine(“发明:{0},组件:{1},计数:{2}”,d.InventionName,d.ComponentName,d.ComponentCount);
}
*/
var=22;
var指数=1;
//想要全面覆盖发明ID、组件ID和应用计数
// 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=
从components中的C选择新的{InventionID=InventionID,ComponentID=C.ID,ComponentName=C.Name}进入allcomps
将新{allcomps.InventionID,allcomps.ComponentID}上的发明组件中的B连接到join1中
//从接头1/内部接头中的j1开始
从join1.DefaultIfEmpty()中的j1开始//导致异常
orderby allcomps.ComponentName
选择新的{
RowNum=index++,
发明ID=所有公司发明ID,
ComponentName=allcomps.ComponentName,
组件计数=j1.计数,
};
foreach(corpcheck中的变量x)
{
WriteLine(“发明ID:{0},RowNum:{1},组件名称:{2},计数:{3}”,x.InventionID,x.RowNum,x.ComponentName,x.ComponentCount);
}
}
公共类发明
{
公共int ID{get;set;}
公共字符串名称{get;set;}
}
公共类发明组件
{
公共int ID{get;set;}
public int InventionID{get;set;}
公共int组件ID{get;set;}
公共整数计数{get;set;}
}
公共类组件
{
公共int ID{get;set;}
公共字符串名称{get;set;}
}
}

通过添加
DefaultIfEmpty()
j1
对于某些组件可以为空。如果
j1
为空,我假设您希望计数为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对象中,您还可以使用
ComponentCount=j1?.Count??0
。但我假设您将在LINQ中使用它作为SQL后端