我如何 select 将多个列表中的不同且有序的数据转换为通用列表?

How can I select distinct and ordered data into a generic list from multiple lists?

我有几个通用列表,其中包含一些共享数据和一些独特数据。它们包含相同 class 的数据,但使用不同的参数(单位)填充。所以所有的通用列表都是这种类型的:List<PriceVarianceData>

我想对这些通用列表做的是从中提取它们包含的不同数据的子集,然后对合并后的列表进行排序。

更具体地说,要查询的列表具有以下结构:

public class PriceVarianceData
{
    public String Unit { get; set; }
    public String ShortName { get; set; }
    public String ItemCode { get; set; }
    public String Description { get; set; }
    public String PriceWeek { get; set; }
    public String Week { get; set; }
    public String Price { get; set; }
    public String Variance { get; set; }
    public String VarianceAverage { get; set; }
    public int RegionOrder { get; set; }
    public int ContractPrice { get; set; }
}

...我要将数据提取到其中的通用列表具有以下结构:

public class PriceVarianceSupersetDisplayData
{
    public String ShortName { get; set; }
    public String ItemCode { get; set; }
    public String Description { get; set; }
}

这些单位将有一些相同的 ShortName + ItemCode + Description 值,我只想要这些值的唯一组合 - ShortName + ItemCode + Description 不应有重复,但是如果它们有任何不同的值,他们将被视为 unique/distinct。所以提取的数据在排序后应该如下所示:

SHORTNAME   ITEMCODE    DESCRIPTION
---------   --------    -----------
Fakeroo     001         Stratoblaster
Fender      001         Stratocaster
Gibson      001         335
Gibson      001         SG
Fender      002         Telecaster
Gibson      002         Les Paul
Carvin      003         Knife
Carvin      003         L6S

我[认为我]知道我在这里需要的是 LINQ 查询;在伪代码中,类似于:

List<PriceVarianceSupersetDisplayData> displayDataAmalgamated = select distinct ShortName, ItemCode, Description from craftworksPVDList, chophousePVDList, gordonbierschPVDList, oldchicagoPVDList, oldchifranchisePVDList, rockbottomPVDList order by ItemCode then by ShortName, then by Description

...但不知道如何将其从伪代码转换为真正的 LINQ。

更新

user3185569 和 Zoran Horvat 的答案组合似乎有效,只是我显然没有得到不同的值。我的第一个线索是最终通用列表中的条目数似乎太高了。

然后我查看了列表中的前两个条目,它们(或至少看起来)相同:

这是我正在使用的代码;如前所述,它是提供的前两个答案的组合:

private List<PriceVarianceSupersetDisplayData> GetSharedDisplayDataForAll()
{
    Func<PriceVarianceData, PriceVarianceSupersetDisplayData> selector =
        (p => new PriceVarianceSupersetDisplayData()
        {
            ShortName = p.ShortName,
            ItemCode = p.ItemCode,
            Description = p.Description
        });

    List<PriceVarianceSupersetDisplayData> displayDataAmalgamated =
            craftworksPVDList.Concat(chophousePVDList)
                            .Concat(chophousePVDList)
                            .Concat(gordonbierschPVDList)
                            .Concat(oldchicagoPVDList)
                            .Concat(oldchifranchisePVDList)
                            .Concat(rockbottomPVDList).Select(selector)
                            .Distinct()
                            .OrderBy(x => x.ItemCode)
                            .ThenBy(x => x.ShortName)
                            .ThenBy(x => x.Description).ToList();

    return displayDataAmalgamated;
}

为什么 Distinct() return 重复值?

如果您希望构建另一个仅包含来自所有部分列表的不同数据的列表,那么您可以通过使用 Concat LINQ 方法将它们连接在一起,然后最后使用 Distinct LINQ 方法轻松地做到这一点:

list1.Concat(list2).Concat(list3)...Concat(listN).Distinct();

这假定 类 是值类型。由于您使用的是非常简单的 类,我建议您在其中实现值类型语义,然后这种列表操作将变得微不足道。

添加值类型语义意味着重写 GetHashCode、Equals、添加运算符 == 和运算符 != 并最好用自己的 Equals(T) 实现 IEnumerable。完成所有这些后,正如我所说,列表操作将变得微不足道。

数据排序可以加在最后,如:

list1
    .Concat(list2)
    .Concat(list3)
    ...
    .Concat(listN)
    .Distinct()
    .OrderBy(x => x.ItemCode)
    .ThenBy(x => x.ShortName)
    .ThenBy(x => x.Description);

方法一:修改模型

首先执行 EqualsGetHashCode。然后,您可以 add 所有列表到一个列表, select 三个键并使用 Distinct 删除重复项并使用 OrderBy - ThenBy 进行排序:

public class PriceVarianceSupersetDisplayData
{
    public String ShortName { get; set; }
    public String ItemCode { get; set; }
    public String Description { get; set; }

    public override bool Equals(object obj)
    {
        var pv = obj as PriceVarianceSupersetDisplayData;

        if (pv == null)
            return false;

        return this.ShortName == pv.ShortName
            && this.ItemCode == pv.ItemCode
            && this.Description == pv.Description;
    }

    public override int GetHashCode()
    {
        return 0;
    }
}

Func<PriceVarianceData, PriceVarianceSupersetDisplayData> selector =
                (p => new PriceVarianceSupersetDisplayData()
                {
                    ShortName = p.ShortName,
                    ItemCode = p.ItemCode,
                    Description = p.Description
                });

List<PriceVarianceSupersetDisplayData> results =
            craftworksPVDList.Concat(chophousePVDList)
                            .Concat(gordonbierschPVDList)
                            .Concat(oldchicagoPVDList)
                            .Concat(oldchifranchisePVDList)
                            .Concat(rockbottomPVDList).Select(selector).Distinct()
                            .OrderBy(x=> x.ItemCode).ThenBy(x=> x.ShortName)
                            .ThenBy(x=> x.Description).ToList();

方法二:不修改模型

(无需通过使用 Tuple 的等式来实现 EqualsGetHashCode

List<PriceVarianceSupersetDisplayData> results = 
        craftworksPVDList.Concat(chophousePVDList)
                        .Concat(gordonbierschPVDList)
                        .Concat(oldchicagoPVDList)
                        .Concat(oldchifranchisePVDList)
                        .Concat(rockbottomPVDList).Select(x=> Tuple.Create(x.ShortName, x.ItemCode, x.Description))
                        .Distinct().OrderBy(x=> x.Item2).ThenBy(x=> x.Item1).ThenBy(x=> x.Item3)
                        .Select(t=> new PriceVarianceSupersetDisplayData()
                        {
                            ShortName = t.Item1,
                            ItemCode = t.Item2,
                            Description = t.Item3
                        }).ToList();

如果您不想打扰 Equals / GetHashCode 覆盖或实施 IEqualityComparer<PriceVarianceSupersetDisplayData,这是使 Distinct 在其他答案中正常工作所必需的,最简单的方法是使用中间匿名类型投影(因为编译器会自动为匿名类型实现正确的按值比较语义),如下所示:

var displayDataAmalgamated =
    new[] { craftworksPVDList, chophousePVDList, gordonbierschPVDList, oldchicagoPVDList, oldchifranchisePVDList, rockbottomPVDList }
    .SelectMany(list => list.Select(item => new { item.ShortName, item.ItemCode, item.Description }))
    .Distinct()
    .Select(item => new PriceVarianceSupersetDisplayData { ShortName = item.ShortName, ItemCode = item.ItemCode, Description = item.Description })
    .OrderBy(item => item.ShortName).ThenBy(item => item.ItemCode)
    .ToList();

或使用 group by:

的查询语法
var displayDataAmalgamated = (
    from list in new[] { craftworksPVDList, chophousePVDList, gordonbierschPVDList, oldchicagoPVDList, oldchifranchisePVDList, rockbottomPVDList }
    from item in list
    group item by new { item.ShortName, item.ItemCode, item.Description } into g
    orderby g.Key.ShortName, g.Key.Description
    select new PriceVarianceSupersetDisplayData { ShortName = g.Key.ShortName, ItemCode = g.Key.ItemCode, Description = g.Key.Description }
    ).ToList();