【问题标题】:Linq left join returns inner joinLinq 左连接返回内连接
【发布时间】:2018-02-21 00:53:19
【问题描述】:

我正在尝试使用 Linq 在 C# 中执行外连接,指导我的人一直说我不应该尝试执行外连接,这并不是真正的答案。

我从其他线程得到的是,我需要.DefaultIfEmpty(),而我可能没有记录。

我首先在可能缺少信息的行上进行了尝试,然后将其添加到每一行以查看是否是问题所在。

每次我运行它时,我只得到内部连接记录。除了不包括我的数据库中仅在前两个表中包含信息的两条记录之外,它的效果很好。

var sqlQuery =
from s in ctx.Suppliers
from sp in ctx.SupplierParts
    .Where(sp => sp.SupplierID == s.SupplierID)
    .DefaultIfEmpty()
from sm in ctx.SupplierManufacturerRelations
    .Where(sm => sm.SupplierPNID == sp.SupplierPNID)
    .DefaultIfEmpty()
from mp in ctx.ManufacturerParts
    .Where(mp => mp.MfgPNID.Equals(sm.MfgPNID))
    .DefaultIfEmpty()
from m in ctx.Manufacturers
    .Where(m => m.ManufacturerID.Equals(mp.ManufacturerID))
    .DefaultIfEmpty()
from im in ctx.ItemMasters
    .Where(im => im.PreID == mp.PreID)
    .Where(im => im.PartNumber == mp.PartNumber)
    .DefaultIfEmpty()
from c in ctx.ComponentClasses
    .Where(c => c.CCID == im.CCID)
    .DefaultIfEmpty()
from um in ctx.UnitsOfMeasures
    .Where(um => um.UOMID == sp.UOMID)
    .DefaultIfEmpty()

select new
{ my variables}

var querylist = sqlQuery.Where(n => n.SupplierID == thisSupplier).ToList();

我也试过

from s in ctx.Suppliers
    join sp in ctx.SupplierParts on s.SupplierID equals sp.SupplierID
    join sm in ctx.SupplierManufacturerRelations on sp.SupplierPNID equals sm.SupplierPNID into spartgroup
from sm in spartgroup.DefaultIfEmpty()
    join mp in ctx.ManufacturerParts on sm.MfgPNID equals mp.MfgPNID into mpartgroup
from mp in mpartgroup.DefaultIfEmpty()
     join m in ctx.Manufacturers on mp.ManufacturerID equals m.ManufacturerID into mgroup
from m in mgroup.DefaultIfEmpty()
     join im in ctx.ItemMasters 
     on new { key1 = (int)mp.PreID, key2 = (int)mp.PartNumber }
     equals new { key1 = im.PreID, key2 = im.PartNumber }
     into tpartgroup
from im in tpartgroup.DefaultIfEmpty()
     join c in ctx.ComponentClasses on im.CCID equals c.CCID into fullgroup
from c in fullgroup.DefaultIfEmpty()
     join um in ctx.UnitsOfMeasures on sp.UOMID equals um.UOMID

此 SQL 查询有效且不会省略行

    SELECT Supplier.SupplierID
         , SupplierPart.SupplierPNID
         , SupplierPart.SupplierPN
         , SupplierPart.Description
         , SupplierManufacturerRelation.MfgPNID
         , ManufacturerPart.PreID
         , ManufacturerPart.PartNumber
         , ItemMaster.CCID
         , ItemMaster.Description AS Expr1
      FROM Supplier  
Inner JOIN SupplierPart 
        ON Supplier.SupplierID = SupplierPart.SupplierID 
 Left JOIN SupplierManufacturerRelation 
        ON SupplierPart.SupplierPNID = SupplierManufacturerRelation.SupplierPNID 
 Left JOIN ManufacturerPart 
        ON SupplierManufacturerRelation.MfgPNID = ManufacturerPart.MfgPNID 
 Left JOIN ItemMaster 
        ON ManufacturerPart.PreID = ItemMaster.PreID 
       AND ManufacturerPart.PartNumber = ItemMaster.PartNumber
     WHERE Supplier.SupplierID = 9

【问题讨论】:

  • 首先,如果您想要“翻译”SQL 连接,您必须使用 LINQ join 语句,而不是 where。但是您应该完全避免使用 LINQ join。使用导航属性,即 LINQ 类模型中的关联。
  • 您使用的是 LINQ-to-SQL 还是 EntityFramework 或其他一些 ORM?
  • 我很确定我正在使用 LINQ-to-SQL 我正在使用 EntityFramwork 进行正常的数据库交互,但这是为了避免必须拉出所有表并将它们组合到程序中是我最初做的。
  • 所以您使用的是 EntityFramework 而不是 LINQ-to-SQL,LINQ-to-SQL 本身就是一个 ORM,我非常怀疑您是否同时使用 2 个 ORM - 所以既然您是使用 ORM,按照@GertArnold 告诉的那样做,而不是尝试自己加入表 - 使用 EntityFramework 提供的Navigation Properties,所以你的第一个“加入”应该简单地写成var supplierParts = ctx.Suppliers.SelectMany(x => x.SupplierParts) 会给你一个 SupplierParts 的列表在所有供应商中 - 或者如果您只有一个供应商,那就是 ctx.Suppliers.First().SupplierParts
  • 您是否考虑过将您的 SQL 查询作为用户存储过程保存在数据库中,然后从您的 .net 代码中调用用户存储过程?这也可以让您调整您的 sql 代码,而无需在以后重新编译您的代码。

标签: c# sql-server entity-framework linq


【解决方案1】:

用于将 SQL 转换为 LINQ 查询理解:

  1. 将 FROM 子选择转换为单独声明的变量。
  2. 按 LINQ 子句顺序翻译每个子句,将一元运算符(DISTINCTTOP 等)翻译成应用于整个 LINQ 查询的函数。
  3. 使用表别名作为范围变量。使用列别名作为匿名类型字段名称。
  4. 对多个列使用匿名类型 (new { ... })。
  5. Left Join 是通过使用 into join_variable 来模拟的,然后从 join 变量中执行另一个 from,然后是 .DefaultIfEmpty()
  6. COALESCE 替换为条件运算符和空测试。
  7. IN 转换为.Contains()NOT IN 转换为!...Contains()
  8. SELECT * 必须替换为 select range_variable 或对于连接,一个包含所有范围变量的匿名对象。
  9. SELECT 字段必须替换为 select new { ... } 创建一个包含所有所需字段或表达式的匿名对象。
  10. 必须使用扩展方法处理正确的FULL OUTER JOIN

所以从您的 SQL 来看,您的查询应该如下所示:

var ans = from s in ctx.Suppliers
          join sp in ctx.SupplierParts on s.SupplierID equals sp.SupplierID
          join sm in ctx.SupplierManufacturerRelations on sp.SupplierPNID equals sm.SupplierPNID into smj
          from sm in smj.DefaultIfEmpty()
          join mp in ctx.ManufacturerParts on sm?.MfgPNID equals mp.MfgPNID into mpj
          from mp in mpj.DefaultIfEmpty()
          join im in ctx.ItemMasters on new { key1 = (int)mp.PreID, key2 = (int)mp.PartNumber } equals new { key1 = im.PreID, key2 = im.PartNumber } into imj
          from im in imj.DefaultIfEmpty()
          select new {
              s.SupplierID, sp.SupplierPNID, sp.SupplierPN, sp.Description, sm.MfgPNID, mp.PreID, mp.PartNumber, im.CCID, Expr1 = im.Description
          };

【讨论】:

  • 除非我遗漏了什么,否则这与我上面的第二次尝试相同,添加了 lambda,然后导致错误:错误 CS8072 表达式树 lambda 可能不包含空传播操作
  • 您的第二次尝试不完整,并且与您提供的 SQL 不匹配。对于 null 运算符,我很抱歉,我忘记从代码中删除所有 ? - 我使用 LINQPad 中的 LINQ to Objects 对其进行测试,它不会像 LINQ to SQL 那样处理 null。
  • 我相信我找到了我的终极问题,实际上这两种风格都有效。我发现,在我的选择语句中,我选择了 im.PreID 和 im.PartNumber,它们是不可为空的 int 字段。虽然@NetMage 的示例有效,因为他正确翻译了来自 mp.PreID 和 mp.PartNumber 的选择,它们可以为空。
猜你喜欢
  • 2010-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-16
  • 1970-01-01
  • 1970-01-01
  • 2017-10-01
  • 1970-01-01
相关资源
最近更新 更多