【问题标题】:Left Outer Join, whats the difference between these two approaches?左外连接,这两种方法有什么区别?
【发布时间】:2018-02-28 04:47:24
【问题描述】:

这两种使用 LINQ 执行左外连接的方法有什么区别,因为我都使用两个买家和供应商列表,并通过一个公共区域将它们连接起来以查找位于同一区域的供应商和买家。

class Supplier
{
    public string Name { get; set; }
    public string District { get; set; }
}

class Buyer
{
    public string Name { get; set; }
    public string District { get; set; }
}
List<Buyer> buyers = new List<Buyer>()
{
    new Buyer() { Name = "Johny", District = "Fantasy District" },
    new Buyer() { Name = "Peter", District = "Scientists District" },
    new Buyer() { Name = "Paul", District = "Fantasy District" },
    new Buyer() { Name = "Maria", District = "Scientists District" },
    new Buyer() { Name = "Joshua", District = "EarthIsFlat District" },
    new Buyer() { Name = "Sylvia", District = "Developers District" },
    new Buyer() { Name = "Rebecca", District = "Scientists District" },
    new Buyer() { Name = "Jaime", District = "Developers District" },
    new Buyer() { Name = "Pierce", District = "Fantasy District" }
};
List<Supplier> suppliers = new List<Supplier>()
{
    new Supplier() { Name = "Harrison", District = "Fantasy District" },
    new Supplier() { Name = "Charles", District = "Developers District" },
    new Supplier() { Name = "Hailee", District = "Scientists District" },
    new Supplier() { Name = "Taylor", District = "EarthIsFlat District" }
};

第一:

var suppliersAndBuyers = from s in suppliers
                         orderby s.District
                         join b in buyers on s.District equals b.District into buyersGroup
                         select buyersGroup.DefaultIfEmpty(
                             new Buyer()
                             {
                                 Name = string.Empty,
                                 District = s.District
                             });

foreach (var item in suppliersAndBuyers)
{
    foreach (var buyer in item)
    {
        Console.WriteLine($"{buyer.District} {buyer.Name}");
    }
}

第二种方法:

var suppliersAndBuyers = from s in suppliers
                                 orderby s.District
                                 join b in buyers on s.District equals b.District into buyersGroup
                                 from bG in buyersGroup.DefaultIfEmpty()
                                 select new
                                 {
                                     Name = bG.Name == null ? string.Empty : bG.Name,
                                     s.District,
                                 };

foreach (var item in suppliersAndBuyers)
{
    Console.WriteLine($"{item.District} {item.Name}");
}

两者都产生完全相同的输出,是我们输出结果方式的唯一区别吗?我应该使用哪一个?

编辑:第一种方法返回IEnumerable&lt;IEnumerable&lt;Buyer&gt;&gt;,第二种方法返回IEnumerable&lt;AnonymousType&gt;,这是两者在返回类型方面唯一有意义的区别,这是两者之间的唯一决定因素两种方法,我想要一个类型还是匿名类型?

【问题讨论】:

  • 提示:Name = bG.Name == null ? string.Empty : bG.Name -> Name = bG.Name ?? "".
  • 这是对数据库调用有何影响的假设,还是仅在内存中。
  • @Igor 我正在研究 LINQ atm,所以我没有真正的场景,我正在关注 MSDN 网站上的加入示例,看到了这两种方法,应用了它们,但无法找出区别。
  • 第二次,您再次遍历您的可枚举对象,再次构造所有对象。就个人而言,我总是更喜欢第一个。但是,我也相信它在创建许多空对象并且只是不使用它们时确实使用了不需要的额外内存......
  • @YotamSalmon 有道理,为什么第一个返回 IEnumerable> 而不仅仅是 IEnumerable

标签: c# linq left-join


【解决方案1】:

好的。据我所见:(A)

var suppliersAndBuyers = from s in suppliers
                         orderby s.District

枚举供应商列表。这很明显。现在,将其加入买家列表:

var suppliersAndBuyers = from s in suppliers
                         orderby s.District
                         join b in buyers on s.District equals b.District

这会创建匹配项(一些我不知道类型的对象,因为我面前没有普通的 Visual Studio 实例)。但例如,它就像Harrison:Jonnie, Hailee:Peter, ...。现在我们可以根据这些匹配(由变量 bs 表示)创建一个 IEnumerable 对象,如下所示:

var suppliersAndBuyers = from s in suppliers
                         orderby s.District
                         join b in buyers on s.District equals b.District
                         select new {
                             Supplier = s, Buyer = b
                         }

这将创建一个匿名类型的 IEnumerable,其中每个对象代表一对供应商和买方。

var suppliersAndBuyers = from s in suppliers
                         orderby s.District
                         join b in buyers on s.District equals b.District into buyersGroup

但是您决定做的是左连接,如您的标题中所写。它的作用是获取在 sn-p (A) 中创建的列表中的每个元素,并匹配买家列表中所有匹配对象的IEnumerable。这会产生可枚举的匹配项。例如,对于 Harrisonsuppliers 列表中的第一个条目,您将获得一个 IEnumerable,其中包含 JohnnyPaul刺穿suppliers 列表中的其他元素也是如此,按其District 排序。

这就是您以IEnumerable&lt;IEnumerable&lt;Buyer&gt;&gt; 结尾的原因。因为对于每个供应商(第一个 IEnumerable 维度),您都有一个“列表”Buyer(第二个维度 + 类型解释)

然后合并空条目在我看来是过时的,因为你不应该有nulls,而只是空的IEnumerables,然后在遍历它们时,你不会碰到任何元素。 (虽然我不确定最后一段,因为我从来没有编译过代码所以我不知道)


现在关于合并部分,第一个示例为每个条目创建一个新的Buyer 对象,然后采用DefaultIsEmpty。对于第二个示例,它首先创建第一个维度和第二个维度 FULL IEnumerables,然后在再次迭代时,它会合并空值。正如我在 cmets 中提到的,其中一个不需要的循环。

【讨论】:

  • 感谢 Yotam,消除了对 IEnumerable> 的困惑,问题仍然存在,何时使用这些方法中的任何一种,您在最初的回复中提到不同之处在于第二个一个人会再迭代一次,但是当我用第一种方法打印它们时,我仍然会再迭代一次。一个返回 而另一个返回匿名类型是唯一的“有意义的区别”吗?
  • 是的,实际上对你来说实际的区别是表达式的类型。但只是澄清一下,我告诉你第二个例子做了一个 iteration,这并不真正影响你编写的以下 for 循环。但两者都有 IEnumerable 的 2 个维度。这意味着您将拥有 2 层嵌套的 for。如果你想保存一个级别并且只使用一个for,你可以在第二个代码sn-ps处停下来,而不是左连接,而是进行内连接。然后你会得到一个一维的 IEnumerable 匹配。
【解决方案2】:

在第一个中,由于new Buyer(),您返回了Buyer 中的IEnumerable,而在第二个中,由于select new,您返回了anonymous types 中的IEnumerable。它们各有优缺点。

您可以检查以下答案来决定:

https://stackoverflow.com/a/21443164/2946329

https://stackoverflow.com/a/48677/2946329

【讨论】:

  • 实际上我正在返回 IEnumerable>,但仍然无法回答有什么区别以及何时应用这些方法中的任何一种
  • 或者至少我无法推断出答案:)
  • @mjwills 我明白为什么一个返回买家而另一个返回匿名类型,我只是不明白这是否是这两种方法之间唯一的实际区别?
  • @Darkbound 我看不出除此之外的任何主要区别。请查看链接的参考,以便决定何时使用其中的哪一个。
【解决方案3】:

好的,所以我花了一些时间来破译这个:)

所以事情是在你正在做的第一个例子中

选择买家组.DefaultIfEmpty( 新买家() { 名称 = 字符串。空, 区 = s.District });

该行的含义是...选择所有买家组,如果为空则返回new Buyer() { Name = string.Empty, District = s.District }(您将其定义为默认值)

第二个例子

来自buyersGroup.DefaultIfEmpty() 中的bG 选择新的 { 名称 = bG.Name == 空? string.Empty : bG.Name, s.区, };

如果组为空,您首先定义默认值 BuyerGroup.DefaultIfEmpty(),然后才进行选择。密切注意 DefaultIfEmpty 括号括起来的内容。

编辑

我似乎根本找不到使用 DefaultIfEmpty 的理由...不要认为您可以在左侧获取 null 来需要它...而且它可能会使代码更容易理解。

【讨论】:

    【解决方案4】:

    Enumerable.DefaultIfEmpty。在您的第一个示例中,如果在连接中找不到Buyer 实例(即您的外部连接),则将new Buyer 作为默认值传递给该扩展方法以在连接中返回。之后没有选择语句,因此连接的结果在您的结果中建模为IEnumerable&lt;IEnumerable&lt;Buyer&gt;&gt;

    您的第二个查询确实使用了带有匿名投影的select,这就是它导致集合扁平化的原因。

    只看你得到的类型

    • 您可以通过删除第二个查询中的select 来模仿第一个查询的结果

      from s in suppliers
      orderby s.District
      join b in buyers on s.District equals b.District into buyersGroup
      from bG in buyersGroup.DefaultIfEmpty();
      
    • 如果想换个方向,您可以在第一个查询的末尾添加第二个查询中的相同选择语句。

      from s in suppliers
      orderby s.District
      join b in buyers on s.District equals b.District into buyersGroup
      select buyersGroup.DefaultIfEmpty(
          new Buyer()
          {
              Name = string.Empty,
              District = s.District
          ))
      select new
      {
          Name = bG.Name == null ? string.Empty : bG.Name,
          s.District,
      };
      

    至于为什么使用一个而不是另一个,这是一个见仁见智的问题,也取决于情况。在没有任何其他上下文的非常简单的示例中,使用投影将是更易读的答案。如果您想将结果传递给另一个方法或从它正在执行的方法返回,那么IEnumerable&lt;IEnumerable&lt;Buyer&gt;&gt; 将是唯一的方法(如果受限于这两个样本)。如果您必须对数据存储执行此操作,那么我建议您对查询进行分析,以查看正在执行的查询是什么并对其进行分析。

    简而言之,在您遇到特定的现实世界情况之前,没有正确/错误的答案,其中这两个与该度量之间存在可测量的差异,并且应用于它的权重取决于该情况。

    【讨论】:

    • 谢谢,我明白,目前唯一剩下的问题(这是我的主要问题)是何时使用这两种方法中的任何一种。这两种方法之间唯一的“有意义的区别”是否在于其中一种返回某种类型而另一种返回匿名类型?
    猜你喜欢
    • 2013-09-08
    • 2019-03-31
    • 1970-01-01
    • 1970-01-01
    • 2017-02-26
    • 2020-02-14
    • 2011-01-10
    • 2016-07-01
    相关资源
    最近更新 更多