【问题标题】:Searching for an item in a list that's nested inside another list based on a property value in C# using LINQ?使用 LINQ 根据 C# 中的属性值搜索嵌套在另一个列表中的列表中的项目?
【发布时间】: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();

【问题讨论】:

    标签: c# linq


    【解决方案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 调用会抛出异常。
      猜你喜欢
      • 2022-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多