Is there a way to search for an item in a list that's nested inside another list based on a property value using LINQ?
Given the follow models below, for a given Order (variable customerOrder
), I want to return the earliest order date (Date
) where the Day is "Sunday".
models:
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; }
}
code:
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();
CodePudding user response:
EDIT More elegant query
You can use Linq to achieve your result.
Here is a query that would closely mimick your code.
customerOrder.OrderLines
.Select(ol => ol.OrderDates
.Where(x => x.Day.Equals("Sunday"))
.FirstOrDefault())
.Where(d => d != null)
.OrderBy(d => d.Date)
.FirstOrDefault();
which could be more elegantly rewritten as:
customerOrder.OrderLines
.SelectMany(ol => ol.OrderDates)
.OrderBy(d => d.Date)
.FirstOrDefault(d => d.Day == "Sunday");
Here is a Linqpad query with some test data and dump for you to try. Simply copy and paste in 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; }
}
On a side note the OrderDate
class is not necessary. The DateTime
type has a property DayOfWeek
that you can use to test is a Date is a Sunday.
DayOfWeek
is an enum so you can simply test MyDate.DayOfWeek == DayOfWeek.Sunday
rather than relying on a string
for that purpose.
CodePudding user response:
First of all your code will not work as intended.
dates.OrderBy(d => d.Date);
doesn't work: OrderBy
returns an IEnumerable, it doesn't change the original collection. You should either use List.Sort
` or do this:
dates = dates.OrderBy(d => d.Date).ToList()
Secondly, you use FirstOrDefault
: it has an overload that accepts predicate to search with; so the Where
call is not needed. In addition FirstOrDefault will return null
if nothing found. If this is a possible scenario, you should consider checking whether orderDate
is null:
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();
That should work fine. But it hard to guess what aspects of behavior of your code samples are intended and what are not. You ask about searching, but say nothing about OrderBy part. Could you clarify this part, please?
Answering the question, if by better
you mean more compact way, you can go with something like this:
var result = customerOrder.OrderLines
.SelectMany(a => a.OrderDates)
.OrderBy(d => d.Date)
.FirstOrDefault(x => x.DateTypeId.Equals("Sunday"));
return result;
You shouldn't be bothered with better way
now; firstly you should start with at least working way
. I suggest you to learn how to do these things both using Linq and without using Linq.
CodePudding user response:
Better is a bit subjective, but you can use the Enumerable.SelectMany
extension method to flatten the OrderDate
instances into one sequence.
Then you can use the Enumerable.Where
extension method to filter the dates that are "Sunday"
.
Then you can use the Enumerable.Min
extension method to get the minimum date.
All of this can be chained together into a single statement.
DateTime earliestSunday = customeOrder
.OrderLines
.SelectMany(ol => ol.OrderDates)
.Where(od => od.Day == "Sunday")
.Min(od => od.Date);