左外连接,这两种方法的区别是什么?

时间:2018-02-27 20:10:57

标签: c# linq left-join

这两种方法在使用LINQ执行Left Outer Join方面有何不同,因为我使用了两个买方和供应商列表,并通过公共区域加入它们以查找位于同一区域的供应商和买方。

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<IEnumerable<Buyer>>,第二种方法返回IEnumerable<AnonymousType>,这是两者之间唯一有意义的区别,它们返回的是什么类型,这是唯一的两种方法之间的决定因素,我想要一种类型或匿名类型?

4 个答案:

答案 0 :(得分:3)

好的。从我看到的内容:(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

但是你决定做的是左连接,如你的标题所示。它的作用是,它会在列表(A)中创建列表中的每个元素,并从买方列表中匹配所有匹配对象的IEnumerable。这产生了一系列匹配。例如,对于 Harrison suppliers列表中的第一个条目,您将获得包含 Johnny 的<{1}}, Paul < / em>和皮尔斯。与IEnumerable列表中的其他元素相同,按其suppliers排序。

这就是你以District结束的原因。因为对于每个供应商(第一个IEnumerable维度),您有一个IEnumerable<IEnumerable<Buyer>>的“列表”(第二维+类型解释)

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

现在,对于合并部分,第一个示例为每个条目创建一个新的IEnumerable对象,然后取Buyer。对于第二个示例,它首先创建第一个维度和第二个维度FULL IEnumerables,然后再次迭代时,它合并空值。正如我在评论中提到的那样,这是一个不必要的循环。

答案 1 :(得分:2)

在第一个中,由于IEnumerable,您返回Buyer new Buyer(),而在第二个中,您返回了IEnumerable select new 3}}因为%let usager_entr =12121212,34343434,56565656; %put &usager_entr; %let New_usager_entr=%sysfunc(prxchange(s/([1-9]+)/"$1"/, -1,%quote(&usager_entr))); %put &New_usager_entr; 。他们有利有弊,优点和缺点。

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

anonymous types

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

答案 2 :(得分:2)

好的,我花了一段时间来解读这个:)

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

  

选择buyersGroup.DefaultIfEmpty(                                新买家()                                {                                    Name = string.Empty,                                    区= s.District                                });

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

在第二个例子中

  

来自买家小组中的bG.DefaultIfEmpty()                                    选择新的                                    {                                        Name = bG.Name == null? string.Empty:bG.Name,                                        s.District,                                    };

如果group为emptypilot.DefaultIfEmpty()并且仅在执行select之后,您首先定义默认值。密切关注DefaultIfEmpty括号内容。

修改

我似乎无法找到一个完全使用DefaultIfEmpty的理由......不要认为你可以在左侧获得null而需要它...而且它可能会使代码稍微容易一些了解。

答案 3 :(得分:2)

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

您的第二个查询确实使用带有匿名投影的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();
    
  • 要转向另一个方向,您可以在第一个查询的末尾添加相同的select语句。

    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<IEnumerable<Buyer>>将是唯一的方法(如果约束到这两个样本)。如果您必须使用数据存储执行此操作,那么我建议您对查询进行概要分析,以查看正在执行的查询和配置文件。

简而言之,没有正确/错误的答案,直到你有一个特定的现实世界的情况,这两者之间存在可衡量的差异,而且该措施的重量取决于那种情况。