如果子元素为空,则SelectMany丢失父级

时间:2018-02-23 06:47:04

标签: c# linq

使用selectmany展平列表,如何获取父对象,当子对象为null时?

我需要显示清单:

"付款人:付款人A,状态:有效"
"付款人:付款人A,状态:最高"
"付款人:付款人A,状态:快速"
"付款人:付款人B,状态:" - "

// Parent class
public class Payer
{
    public string Name { get; set; }
    public List<Status> Status { get; set; }
}

// Child class
public class Status
{
    public string Name { get; set; }
}

static void Main(params string[] args)
{
    // Lets build some example-data
    List<Payer> payers = new List<Payer>()
    {
        new Payer()
        {
            Name = "Payer A",
            Status = new List<Status>()
            {
                new Status() { Name = "Active" },
                new Status() { Name = "Top" },
                new Status() { Name = "Fast" }
            }
        },
        new Payer()
        {
            Name = "Payer B",
            Status = new List<Status>()
            {
                // Payer B got no Status
            }
        }
    };

    var payerStatuses = payers.SelectMany
    (
        payer => payer.Status, // Select the Children
        (payer, stat) => new { Name = payer.Name, Status = stat.Name } // Tell Linq what to take from parent (payer) and what to take from child (status)
    );

    // let's see what we got
    foreach (var payerStatus in payerStatuses)
    {
        Console.WriteLine("Payer: {0}, Status: {1}", payerStatus.Name, payerStatus.Status);
    }
    // Result:
    // "Payer: Payer A, Status: Active"
    // "Payer: Payer A, Status: Top"
    // "Payer: Payer A, Status: Fast"
    //  But I need payer B too!

   }

2 个答案:

答案 0 :(得分:1)

我在猜你想做什么。您想要一个付款人状态对象。你想看到付款人B的三个对象非A,对吗?

你不应该混合使用linq-styles。这里有一些如何使用SelectMany的例子:

// Parent class
public class Payer
{
    public string Name { get; set; }
    public List<Status> Status { get; set; }
}

// Child class
public class Status
{
    public string Name { get; set; }
}

static void Main(params string[] args)
{
    // Lets build some example-data
    List<Payer> payers = new List<Payer>()
    {
        new Payer()
        {
            Name = "Payer A",
            Status = new List<Status>()
            {
                new Status() { Name = "Active" },
                new Status() { Name = "Top" },
                new Status() { Name = "Fast" }
            }
        },
        new Payer()
        {
            Name = "Payer B",
            Status = new List<Status>()
            {
                // Payer B got no Status
            }
        }
    };

    var payerStatuses = payers.SelectMany
    (
        payer => payer.Status.DefaultIfEmpty(), // Select the Children. If no status, we want an empty list
        (payer, stat) => new { Name = payer.Name, Status = stat == null ? null : stat.Name } // Tell Linq what to take from parent (payer) and what to take from child (status). check if status is not null, because we receive an empty status-list for payers without status
    );

    // let's see what we got
    foreach (var payerStatus in payerStatuses)
    {
        Console.WriteLine("Payer: {0}, Status: {1}", payerStatus.Name, payerStatus.Status);
    }
    // Expected:
    // "Payer: Payer A, Status: Active"
    // "Payer: Payer A, Status: Top"
    // "Payer: Payer A, Status: Fast"
    // "Payer: Payer B, Status: "
}

答案 1 :(得分:0)

所以你有付款人,每个付款人都有零个或多个PayerStatuses。

  

我需要使用selectmany,所以我可以根据状态获取行

不知道这意味着什么。你开始提到解决方案,然后你开始说出你想要的,但解决方案不起作用?也许下次首先指定你想要的东西,然后你尝试了什么,然后告诉我们为什么它不起作用。

除此之外,你没有告诉我们你想要什么,你也忘了写你的付款人和你的PayerStatus课程。

您是否拥有“使用此状态的付款人”课程,就像您使用实体框架时一样?或者你只有两张单独的桌子?

如果您只有两个单独的表,那么您如何表明PayerStatus属于哪个Payer?每个PayerStatus是否属于一个Payer(一对多关系),或者是否有可能属于多个付款人的PayerStatusses(多对多)或者PayerStatusses根本不属于付款人吗?

让我们假设您的关系是最常见的:您在Payers和PayerStatuses之间存在一对多的关系:每个付款人都有零个或多个Payerstatuses;每个PayerStatus都属于一个付款人。

在表格中,这通常使用主键和外键来解决:

class Payer
{
    public int Id {get; set;}  // primary key

    ... // other Payer properties
}

class PayerStatus
{
    public int Id {get; set;}  // primary key

    // every PayerStatus belongs to exactly one Payer using foreign key:
    public int PayerId {get; set;}

    ... // other properties
}

如果您有这样的表格,那么为了获得“每个付款人使用他的PayerStatuses”,您就可以GroupJoin

// your two tables: Payers and Statuses:
IEnumerable<Payer> payers = ...
IEnumerable<PayerStatus> statuses = ...

// GroupJoin these two tables:
var prayersWithTheirStatusses = payers.GroupJoin(statusses,
    payer => payer.Id,              // from each payer take the Id
    status => status.PayerId,       // from each status take the PayerId

    (payer, statusses) =>  new      // when they match, make a new object:
    {
        // take only the Payer properties you plan to use, for instance:
        PayerId = payer.Id,
        Name = payer.Name,
        Reputation = payer.Reputation,
        ...

        // from all payer's statuses, take only the properties you plan to use
        Statusses = statusses.Select(status => new
        {
            Description = status.Description,
            StatusValue = status.Value,
            ...
        })
        .ToList(),
    }
}

这样,即使付款人根本没有任何身份,您也可以获得所有付款人的所有状态。

因此,如果您有以下表格:

Payers
Id = 10, Name = A
Id = 11, Name = B
Id = 12, Name = C

PayerStatusses
Id = 21, PayerId = 10, ...
Id = 22, PayerId = 11, ...
Id = 23, PayerId = 10, ...

上面的linq语句将为您提供类似

的内容
Payer 10, name A with Status list containing data from statuses 21 and 23
Payer 11. name B with Status list with one element containing data from status 22
Payer 12, name C with empty status list

很多时候,人们不希望付款人拥有他的状态,而是每个付款人一行和他的一个状态。所以他们喜欢以下结果

10 - A - status 21
10 - A - status 23
11 - B - status 22
12 - C - (null)

我从来没有找到一个正确的用例,你更喜欢在GroupJoin之上。我认为这仍然需要,因为人们认为没有联系的SQL语句的局限性可以让“付款人拥有他们的状态”,所以他们采取了下一个最好的事情,Left Outer Join, as is answered several times on SO

继GroupJoin之后:

.SelectMany(payer => payer.Statusses.DefaultIfEmpty(),
    (payer, status) => new
    {
        // payer properties:
        PayerId = payer.PayerId,
        PayerName = payer.Name,
        Reputation = payer.Reputation,

        // status properties:
        StatusDescription = status.Description,
        StatusValue = status.Value,
    });