使用 LINQ 根据 C# 中的属性值搜索嵌套在另一个列表中的列表中的项目?

Posted

技术标签:

【中文标题】使用 LINQ 根据 C# 中的属性值搜索嵌套在另一个列表中的列表中的项目?【英文标题】:Searching for an item in a list that's nested inside another list based on a property value in C# using LINQ? 【发布时间】:2021-12-28 16:43:03 【问题描述】:

有没有办法使用 LINQ 根据属性值搜索嵌套在另一个列表中的列表中的项目?

鉴于以下模型,对于给定的订单(变量customerOrder),我想返回最早的订单日期(Date),其中日期是“星期日”。

型号:

public class Order

    public int Id  get; set; 
    public List<OrderLine> OrderLines  get; set; 


public class OrderLine

    public string Description  get; set; 
    public List<OrderDate> OrderDates  get; set; 


public class OrderDate

    public DateTime Date  get; set; 
    public string Day  get; set; 

代码:

var dates = new List<DateTime>();
foreach(var a in customerOrder.OrderLines)

    var orderDate = a.OrderDates.Where(x => x.DateTypeId.Equals("Sunday")).FirstOrDefault();
    dates.Add(orderDate.ActualDate);

dates.OrderBy(d => d.Date);

return dates.FirstOrDefault();

【问题讨论】:

【参考方案1】:

编辑 更优雅的查询

您可以使用 Linq 来实现您的结果。

这是一个与您的代码非常相似的查询。

customerOrder.OrderLines
    .Select(ol => ol.OrderDates
        .Where(x => x.Day.Equals("Sunday"))
        .FirstOrDefault())
    .Where(d => d != null)
    .OrderBy(d => d.Date)
    .FirstOrDefault();

可以更优雅地改写为:

customerOrder.OrderLines
    .SelectMany(ol => ol.OrderDates)
    .OrderBy(d => d.Date)
    .FirstOrDefault(d => d.Day == "Sunday");

这是一个带有一些测试数据和转储的 Linqpad 查询供您尝试。 只需复制并粘贴到 Linqpad 中。

void Main()

    var customerOrder = new Order
    
        Id = 1,
        OrderLines = Enumerable
            .Range(0, 10)
            .Select(i => new OrderLine
            
                Description = $"Line Description i",
                OrderDates = Enumerable.Range(0, 10)
                    .Select(j => new OrderDate
                    
                        Date = DateTime.Now.AddDays(i+j),
                        Day = DateTime.Now.AddDays(i+j).DayOfWeek.ToString()
                    )
                    .ToList()
            )
            .ToList()
        
        .Dump();

    customerOrder.OrderLines
        .SelectMany(ol => ol.OrderDates)
        .OrderBy(d => d.Date)
        .FirstOrDefault(d => d.Day == "Sunday")
        .Dump();


// You can define other methods, fields, classes and namespaces here

public class Order

    public int Id  get; set; 
    public List<OrderLine> OrderLines  get; set; 


public class OrderLine

    public string Description  get; set; 
    public List<OrderDate> OrderDates  get; set; 


public class OrderDate

    public DateTime Date  get; set; 
    public string Day  get; set; 

另一方面,OrderDate 类不是必需的。 DateTime 类型有一个属性 DayOfWeek,您可以使用它来测试 Date is a Sunday。

DayOfWeek 是一个枚举,因此您可以简单地测试MyDate.DayOfWeek == DayOfWeek.Sunday,而不是为此目的依赖string

【讨论】:

在效率方面与我的方法相比如何,或者可以忽略不计? 我会说它应该是可比的。但是,如果您真的想确定,则需要对其进行基准测试。我使用 BenchmarkDotNet 进行此类活动:github.com/dotnet/BenchmarkDotNet。但除非有充分的理由,否则我真的不会担心性能。 @Alexander 也提出了一个很好的观点,即 Linq 查询不会改变所查询的数据(请参阅他的答案的开头)。他指出了您实施的一个错误(@Alenxander,我对您的回答做了 +1,并根据您和@JoshuaRobinson 编辑了我的回答以提出更优雅的要求)。【参考方案2】:

首先,您的代码将无法按预期工作。 dates.OrderBy(d =&gt; d.Date); 不起作用:OrderBy 返回一个 IEnumerable,它不会更改原始集合。您应该使用List.Sort` 或这样做:

dates = dates.OrderBy(d => d.Date).ToList()

其次,您使用FirstOrDefault:它有一个接受谓词进行搜索的重载;所以不需要Where 调用。此外,如果没有找到,FirstOrDefault 将返回 null。如果这是一种可能的情况,您应该考虑检查orderDate 是否为空:

var dates = new List<DateTime>();
foreach(var a in customerOrder.OrderLines)

    var orderDate = a.OrderDates.FirstOrDefault(x => x.DateTypeId.Equals("Sunday"));
    if (orderDate is )
    
        dates.Add(orderDate.ActualDate);
    

dates = dates.OrderBy(d => d.Date).ToList();

return dates.FirstOrDefault();

这应该可以正常工作。但是很难猜测代码示例的行为的哪些方面是有意的,哪些不是。您询问搜索,但对 OrderBy 部分只字未提。请您澄清一下这部分吗?

回答这个问题,如果 better 你的意思是更紧凑的方式,你可以这样:

var result = customerOrder.OrderLines
    .SelectMany(a => a.OrderDates)
    .OrderBy(d => d.Date)
    .FirstOrDefault(x => x.DateTypeId.Equals("Sunday"));
return result;

你现在不应该被better way 困扰;首先你应该至少从working way开始。我建议你学习如何使用 Linq 和不使用 Linq 来做这些事情。

【讨论】:

【参考方案3】:

Better 有点主观,但您可以使用Enumerable.SelectMany 扩展方法将OrderDate 实例扁平化为一个序列。

然后您可以使用Enumerable.Where 扩展方法过滤"Sunday" 的日期。

那么你可以使用Enumerable.Min扩展方法来获取最小日期。

所有这些都可以链接到一个语句中。

DateTime earliestSunday = customeOrder
   .OrderLines
   .SelectMany(ol => ol.OrderDates)
   .Where(od => od.Day == "Sunday")
   .Min(od => od.Date);

【讨论】:

注意如果集合不包含任何元素,Min 调用会抛出异常。

以上是关于使用 LINQ 根据 C# 中的属性值搜索嵌套在另一个列表中的列表中的项目?的主要内容,如果未能解决你的问题,请参考以下文章

C# 如何在另一个 Linq 语句中的 XmlAttributeCollection 上使用 Linq?

LINQ查询中的C#多个OR条件[重复]

从列中获取值并根据值是不是正确显示该行中的所有内容(LINQ - c# web API)

在值中搜索字符串并在 VB.NET 中的 LINQ to XML 中获取属性值

C# linq 查询,每个属性的 where 子查询

Linq 查询以返回具有特定属性值的嵌套数组