Search code examples
c#linqsystem.reflectionjagged-arrays

Trouble understanding how to access nested list in list


I processing a soap response which the xml has a purchase order within items eg

<PurchaseOrder>
<WHID>2</WHID>
<Supplier_ID>00</Supplier_ID>
<POID>6</POID>
<CreateDate>2013-01-02T10:48:27.37+11:00</CreateDate>
<CurrencyName>Australian Dollars</CurrencyName>
<ShippingStatus>Departed</ShippingStatus>
<payment_terms></payment_terms>
<shipping_terms></shipping_terms>
<POStatus>Back-Order</POStatus>
<PurchaseOrderItems>
<PurchaseOrderItem>
<SKU>Shoe30</SKU>
<Product_ID>124064</Product_ID>
<QtyOrdered>9</QtyOrdered>
<QtyOutstanding>6</QtyOutstanding>
<BuyPriceEx>20.0000</BuyPriceEx>
<DirectCosts>0.0000</DirectCosts>
<SupplierBuyPrice>20.0000</SupplierBuyPrice>
</PurchaseOrderItem>
</PurchaseOrderItems>
</PurchaseOrder>

I have no issues putting this into a jagged list . my classes look like this

    public class PurchaseOrder
    {
        public string WHID { get; set; }
        public string Supplier_ID { get; set; }
        public string POID { get; set; }
        public string CreateDate { get; set; }
        public string CurrencyName { get; set; }
        public string ShippingStatus { get; set; }
        public string payment_terms { get; set; }
        public string shipping_terms { get; set; }
        public string POStatus { get; set; }
        public List<PurchaseOrderItems> PurchaseOrderItems { get; set; }
    }
    public class PurchaseOrderItems
    {
        public string SKU { get; set; }
        public string Product_ID { get; set; }
        public string QtyOrdered { get; set; }
        public string QtyOutstanding { get; set; }
        public string BuyPriceEx { get; set; }
        public string DirectCosts { get; set; }
        public string SupplierBuyPrice { get; set; }

    }

I fill the purchase order class using the following linq

        List<PurchaseOrder> _orderDetailed = items.Select(po => new PurchaseOrder()
        {
            WHID = (string)po.Element("WHID").ElementValueNull(),
            Supplier_ID = (string)po.Element("Supplier_ID").ElementValueNull(),
            POID = (string)po.Element("POID").ElementValueNull(),
            CreateDate = (string)po.Element("CreateDate").ElementValueNull(),
            CurrencyName = (string)po.Element("CurrencyName").ElementValueNull(),
            payment_terms = (string)po.Element("payment_terms").ElementValueNull(),
            shipping_terms = (string)po.Element("shipping_terms").ElementValueNull(),
            POStatus = (string)po.Element("POStatus").ElementValueNull(),
            PurchaseOrderItems = po.Descendants("PurchaseOrderItem").Select(i => new PurchaseOrderItems()
            {
                SKU = (string)i.Element("SKU").ElementValueNull(),
                Product_ID = (string)i.Element("Product_ID").ElementValueNull(),
                QtyOrdered = (string)i.Element("QtyOrdered").ElementValueNull()
            }).ToList()

        }).ToList();

The problem come when I pass this to a reflection function that write the object to csv. it only writes the PurchaseOrder fields to the file. I have no idea how to access the PurchaseOrderItems fields so I can write them to the file.

I need to achieve the following using the above xml structure.

WHID Supplier_ID POID SKU    Product_ID QtyOrdered
2    00          6    Shoe30 124064     6

I have cut down the fields above just to keep it easy to read. but the goal is to have all the line items and the purchase order header details on the one line.

public void WriteCSV<T>(IEnumerable<T> items, string path)
{
    Type itemType = typeof(T);
    var props = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                        .OrderBy(p => p.Name);

    using (var writer = new StreamWriter(path))
    {
        writer.WriteLine(string.Join(fieldDelimiter, props.Select(p => p.Name)));

        foreach (var item in items)
        {
            writer.WriteLine(string.Join(fieldDelimiter, props.Select(p => p.GetValue(item, null))));
        }
    }
}

I know I am missing how object work here so looking for some direction.

Much appreciated.


Solution

  • Instead of handling it in WriteCSV, you could pre-process the data to flatten (denormalize) it and then pass it to your existing WriteCSV:

    var flatten = l.SelectMany(po => po.PurchaseOrderItems.Select(pi => new {
        po.WHID,
        po.Supplier_ID,
        po.POID,
        pi.SKU,
        pi.Product_ID,
        pi.QtyOrdered,
    }));
    
    WriteCSV(flatten);