views:

1442

answers:

4

Hello, I'm a bit of a LINQ newbie and I was having some trouble with the following. I'm trying to perform a query using LINQ on an XML file and store the results in a list of DataClass objects that match the XML.

I've got an XML file that is defined like this:

<NewDataSet>
    <NewDataTable>
      <Field>Accepted ASNs</Field>
      <Val>59</Val>
      <Order Number="1234" ShipDate="2009/05/21" />
      <Order Number="2190" ShipDate="2009/05/22" />
      <Order Number="1809" ShipDate="2009/05/22" />      
    </NewDataTable>
    <NewDataTable>
      <Field>Rejected ASNs</Field>
      <Val>8</Val>
      <Order Number="2901" ShipDate="2009/05/21" />
      <Order Number="2810" ShipDate="2009/05/24" />
      <Order Number="1419" ShipDate="2009/05/25" />
    </NewDataTable>
    <NewDataTable>
      <Field>Missing ASNs</Field>
      <Val>7</Val>
      <Order Number="2902" ShipDate="2009/05/19" />
      <Order Number="2898" ShipDate="2009/05/20" />
      <Order Number="1296" ShipDate="2009/05/22" />
    </NewDataTable>
 </NewDataSet>

I have created a Data class to support this XML format. What I would like to do is create a LINQ Query to grab these 3 records and store them into a List of my DataClass.

In order to support multiple Order elements, I have my class defined with a generic list of "Order" structs... It looks like this:

    Public Class ASNData
        Private _field As String
        Private _value As String
        Private _orders As List(Of Order)

        Public Property Field() As String
            Get
            Set
        End Property

        Public Property Value() As String
            Get
            Set
        End Property

        Public Property Orders() As List(Of Order)
            Get
            Set
        End Property

        Structure Order
            Private _number As String
            Private _date As Date

            Public Property Number() As String
                Get
                Set
            End Property

            Public Property ShippingDate() As Date
                Get
                Set
            End Property
        End Structure
    End Class

The biggest issue I'm having is figuring out how to grab the 3 order elements and store them into my list of Order structs.

Can anyone point me in the right direction on this?

Thank you.

A: 

To get order elements (from a NewDataTable element), you'd use

 newDataTableElement.Elements("Order")

which will return an IEnumerable<XElement> - you can then use Select to transform each one as normal. To get the attributes, use:

 element.Attribute("Number").Value
 element.Attribute("ShipDate").Value

That will give the value to use as string - you can get XAttribute to do the conversions for you though:

 CType(element.Attribute("Number"), Integer)
 CType(element.Attribute("ShipDate"), DateTime)

I'm not 100% that that will work for your dates, because it may expect a full date and time - worth a try though. Otherwise, just parse the string using DateTime.ParseExact.

I would strongly recommend that you reconsider Order being a structure though - particularly a mutable structure. Any reason for making it a structure rather than a class?

Jon Skeet
With vb.net you can use the @ sign to get attributes as in element.@attributeName, so that would be element.@Number and element.@ShipDate
Dennis Palmer
Cheers Dennis. Nice to learn something :)
Jon Skeet
Jon, Any particular reason why I should consider changing the Order struct to a class?
Overhed
Yes - because mutable structs are evil and will cause you trouble. Seriously, read http://stackoverflow.com/questions/441309/why-are-mutable-structs-evil
Jon Skeet
Ahhh I see. I hadn't given that much thought because the values on these struct objects won't be changing. I actually tried having the properties for it as read-only (added a Constructor with the 2 fields), but I'm using a WebService and the struct didn't like the Serialization.
Overhed
A: 
var result = from order in YourXMLVar.Descendants("NewDataTable")
    .First().Elements("Order") select order;

This should give you an Array of Orders.

Then you should be able to do something like:

foreach(var order in result)
{
  int someVal = Convert.ToInt32(order.Attribute("Number").Value);
}

// EDIT: Obviously, you wouldn't use First(), but instead would iterate through your orders or whatever your logic dictates. I used First() because it made for an easy demo.

Serapth
Question is tagged vb.net
Dennis Palmer
+4  A: 

Here is the code for a Console application with the LINQ query you need. I had to fill in the getters and setters of your class and containing structure, but this is tested working code. To get a List(Of ASNData) just call DataTables.ToList

Of course this will work with any number of Order elements.

Using Structure Order works just fine. I would use a Class, but no need to change it for this to work.

The key part of this code is the LINQ query:

Dim DataTables = From NewDataTable In TestData...<NewDataTable> _
                 Select New ASNData With {.Field = NewDataTable.<Field>.Value, _
                                          .Value = NewDataTable.<Val>.Value, _
                                          .Orders = (From AnOrder In NewDataTable...<Order> _
                                                     Select New ASNData.Order With _
                                                            {.Number = AnOrder.@Number, _
                                                             .ShippingDate = Date.Parse(AnOrder.@ShipDate)}).ToList}

Here is the complete working console app:

Module Module1

Sub Main()
    Dim TestData = <NewDataSet>
                       <NewDataTable>
                           <Field>Accepted ASNs</Field>
                           <Val>59</Val>
                           <Order Number="1234" ShipDate="2009/05/21"/>
                           <Order Number="2190" ShipDate="2009/05/22"/>
                           <Order Number="1809" ShipDate="2009/05/22"/>
                       </NewDataTable>
                       <NewDataTable>
                           <Field>Rejected ASNs</Field>
                           <Val>8</Val>
                           <Order Number="2901" ShipDate="2009/05/21"/>
                           <Order Number="2810" ShipDate="2009/05/24"/>
                           <Order Number="1419" ShipDate="2009/05/25"/>
                       </NewDataTable>
                       <NewDataTable>
                           <Field>Missing ASNs</Field>
                           <Val>7</Val>
                           <Order Number="2902" ShipDate="2009/05/19"/>
                           <Order Number="2898" ShipDate="2009/05/20"/>
                           <Order Number="1296" ShipDate="2009/05/22"/>
                       </NewDataTable>
                   </NewDataSet>

    Dim DataTables = From NewDataTable In TestData...<NewDataTable> _
                     Select New ASNData With {.Field = NewDataTable.<Field>.Value, .Value = NewDataTable.<Val>.Value, _
                                                     .Orders = (From AnOrder In NewDataTable...<Order> _
                                                               Select New ASNData.Order With {.Number = AnOrder.@Number, .ShippingDate = Date.Parse(AnOrder.@ShipDate)}).ToList}

    Console.WriteLine(DataTables.Count)
    Console.ReadLine()


End Sub

Public Class ASNData
    Private _field As String
    Private _value As String
    Private _orders As List(Of Order)

    Public Property Field()
        Get
            Return _field
        End Get
        Set(ByVal value)
            _field = value
        End Set
    End Property

    Public Property Value() As String
        Get
            Return _value
        End Get
        Set(ByVal value As String)
            _value = value
        End Set
    End Property

    Public Property Orders() As List(Of Order)
        Get
            Return _orders
        End Get
        Set(ByVal value As List(Of Order))
            _orders = value
        End Set
    End Property

    Structure Order
        Private _number As String
        Private _date As Date

        Public Property Number() As String
            Get
                Return _number
            End Get
            Set(ByVal value As String)
                _number = value
            End Set
        End Property

        Public Property ShippingDate() As Date
            Get
                Return _date
            End Get
            Set(ByVal value As Date)
                _date = value
            End Set
        End Property
    End Structure
End Class


End Module
Dennis Palmer
Works like a charm. Thank you. I had a feeling I would need a nested query but just wasn't familiar enough with the syntax to write it.
Overhed
Calling .ToList on the nested query was something new for me in this answer, so I learned something also.I would recommend this video series by Beth Massi for getting started with LINQ in Visual Basic. http://msdn.microsoft.com/en-us/vbasic/bb466226.aspx#linq
Dennis Palmer
A: 

`XDocument xmlDoc = XDocument.Load(Server.MapPath("Order.xml"));

List lst= (from data in xmlDoc.Root.Elements("NewDataTable") select new ASNData { Field=data.Element("Field").Value, Value=data.Element("Val").Value, Orders=(from ord in data.Elements("Order") select new Order { Number=ord.Attribute("Number").Value, ShippingDate=Convert.ToDateTime(ord.Attribute("ShipDate").Value)

                     }).ToList<Order>()

 }).ToList<ASNData>();

foreach(ASNData a in lst) {

 Response.Write("Field:"+a.Field+"</br>");
        Response.Write("Value:"+a.Value+"</br>");
  Response.Write("Orders:"+"</br>");

  foreach(Order o in a.Orders)
  {
  Response.Write("OrderNum:"+o.Number+"</br>");
         Response.Write("ShipDate:"+o.ShippingDate+"</br>");
  }

} `