Linq: Match to properties of child object in a list of child objects and return parent object from a list of parents

Viewed 794

I am attempting to write a Linq query to return a parent object identified by matching to a property of one of its child objects. My class is a Sales Order which has a list of Deliveries which in turn has a list of LineItems. I would like to return a Delivery object when there is a match to the Item Number and Line Number properties in a Delivery's LineItems.

Classes:

public class SalesOrderDetail 
{
    [JsonProperty("SalesOrderNumber")]
    public string SalesOrderNumber { get; set; }

    [JsonProperty("PONumber")]
    public string PONumber { get; set; }

    [JsonProperty("Status")]
    public string Status { get; set; }

    [JsonProperty("Deliveries")]
    public List<Delivery> Deliveries { get; set; }
}

public class Delivery
{
    [JsonProperty("DeliveryDocumentNumber")]
    public string DeliveryDocumentNumber { get; set; }

    [JsonProperty("DeliveryStatus")]
    public string DeliveryStatus { get; set; }

    [JsonProperty("Parcels")]
    public List<Parcel> Parcels { get; set; }

    [JsonProperty("LineItems")]
    public List<LineItem> LineItems { get; set; }

    [JsonProperty("ShippedDate")]
    public DateTime ShippedDate { get; set; }
}

public class LineItem
{
    [JsonProperty("DeliveryDocumentLineNumber")]
    public int DeliveryDocumentLineNumber { get; set; }

    [JsonProperty("OrderLineNumber")]
    public int OrderLineNumber { get; set; }

    [JsonProperty("ItemNumber")]
    public string ItemNumber { get; set; }

    [JsonProperty("QuantityOrdered")]
    public int QuantityOrdered { get; set; }

    [JsonProperty("QuantityPicked")]
    public int QuantityPicked { get; set; }

    [JsonProperty("QuantityScanned")]
    public int QuantityScanned { get; set; }

    [JsonProperty("QuantityShipped")]
    public int QuantityShipped { get; set; }

    [JsonProperty("QuantityCanceled")]
    public int QuantityCanceled { get; set; }

    [JsonProperty("LineItemDetails")]
    public List<LineItemDetail> LineItemDetails { get; set; }
}

Example JSON:

{
    "SalesOrderNumber": "0017320457",
    "PONumber": "PON0234250",
    "Status": "Completely Shipped",
    "Deliveries": [
        {
            "DeliveryDocumentNumber": "0088148528",
            "DeliveryStatus": "Shipped",
            "Parcels": [
                {
                    "CartonNumber": "00093456780095683506",
                    "CarrierCode": "FEDG",
                    "TrackingType": "TRACKING",
                    "TrackingNumber": "946346636541",
                    "Weight": 12.22,
                    "WeightUOM": "LB"
                }
            ],
            "LineItems": [
                {
                    "DeliveryDocumentLineNumber": 10,
                    "OrderLineNumber": 10,
                    "ItemNumber": "SYM-LS2208SR20007RUR",
                    "QuantityOrdered": 1,
                    "QuantityPicked": 0,
                    "QuantityScanned": 0,
                    "QuantityShipped": 1,
                    "QuantityCanceled": 0,
                    "LineItemDetails": [
                        {
                            "CartonNumber": "00093456780095683506",
                            "SerialNumber": "SZ3M7Z8",
                            "MACAddress": null,
                            "MACAddressUnformatted": null
                        }
                    ]
                },
            ],
            "ShippedDate": "2021-03-05T03:32:43.903"
        }
    ]
    "Deliveries": [
        {
            "DeliveryDocumentNumber": "0088148351",
            "DeliveryStatus": "Shipped",
            "Parcels": [
                {
                    "CartonNumber": "000934567800956835901",
                    "CarrierCode": "FEDG",
                    "TrackingType": "TRACKING",
                    "TrackingNumber": "946346636443",
                    "Weight": 10.24,
                    "WeightUOM": "LB"
                }
            ],
            "LineItems": [
                {
                    "DeliveryDocumentLineNumber": 20,
                    "OrderLineNumber": 20,
                    "ItemNumber": "ZEB-ZD62142T01F00EZ",
                    "QuantityOrdered": 1,
                    "QuantityPicked": 0,
                    "QuantityScanned": 0,
                    "QuantityShipped": 1,
                    "QuantityCanceled": 0,
                    "LineItemDetails": [
                        {
                            "CartonNumber": "000934567800956835901",
                            "SerialNumber": "D1N204401680",
                            "MACAddress": null,
                            "MACAddressUnformatted": null
                        }
                    ]
                },
            ],
            "ShippedDate": "2021-03-04T03:32:43.903"
        }
    ]
}

The way I started thinking about this is to write the following code. Intellisense states it will return an object of type Delivery, however, a test of the code generates the exception "Sequence contain no elements".

int lineNumber = 10;
string itemNumber = "SYM-LS2208SR20007RUR";

var matchedDelivery = salesOrderDetail.Deliveries.Where(f => f.LineItems == f.LineItems.Where(l => (l.ItemNumber == itemNumber) && (l.OrderLineNumber == lineNumber))).First();
1 Answers

It's probably more like

var matchedDeliveries = salesOrderDetail.Deliveries
  .Where(d => d.LineItems.Any(li => li.ItemNumber == itemNumber && li.OrderLineNumber == lineNumber));

Or if you want one delivery swap the Where for First (or Single if you want an exception if there are multiple)

This should read fairly naturally but it's "in all deliveries get deliveries where any member of the delivery's lineitems have an itemNumber of blah and orderLineNumber of blahblah" if it helps make sense of it

Related