deostroll
deostroll

Reputation: 11975

LINQ to XML: creating complex anonymous type

I've an xml file as follows:

<ProductGroup>
  <Product id="4601A">
    <name>Roses</name>
    <section>Floral</section>
    <price>46</price>
    <PopupImages>
      <PopupImage>img1.jpg</PopupImage>
      <PopupImage>img2.jpg</PopupImage>
    </PopupImages>
    <ImageThumbs>
      <thumb>img1-thm.jpg</thumb>
      <thumb>img2-thm.jpg</thumb>
    </ImageThumbs>
  </Product>
</ProductGroup>

In production the ProductGroup node might contain many Product nodes. For this I kind of want to build a list of an anonymous object that has the following properties:

name 
section
image
thumb

I am able to get a list of Product elements using XDocument.

Dim doc As XDocument = XDocument.Load("ProductsGroups.xml")
Dim lstProducts = from x In doc Where CType(c.Element("price"), Integer) < 54

From here what do I do?

Update:

Let me explain this better. I am not sure if I have communicated this properly.

Taking the above xml example itself. The above code I've written returns all product elements with the specified "where" condition. Now for each XmlElement returned (product) I've to create n-number of anonymous objects. The number n depends on how many children are there for the PopupImages and ImageThumbs nodes. In my case however, the number will be the same. Hence coming back to the above example, I'd get two anonymous objects:

        Anonymous1      Anonymous2
        ----------      ----------
name        Roses           Roses
section     Floral          Floral
image       img1.jpg        img2.jpg
thumb       img1-thm.jpg    img2-thm.jpg

Upvotes: 1

Views: 1447

Answers (2)

Ladislav
Ladislav

Reputation: 418

I'm not familiar with VB.Net, but in C# you'd write something like this:

        XDocument doc = XDocument.Load("D:\\file.xml");
        var lstProducts = from XElement elem in doc.Element("ProductGroup").Elements("Product")
                          where int.Parse(elem.Element("price").Value) < 54
                          select new
                          {
                              name = elem.Element("name").Value,
                              section = elem.Element("section").Value,
                              image = elem.Element("PopupImages").Element("PopupImage").Value,
                              thumb = elem.Element("ImageThumbs").Element("thumb").Value
                          };

Hope this helps.

EDIT: new query that should handle merging PopupImages and ImageThumbs:

       var lstProducts = from XElement elem in doc.Element("ProductGroup").Elements("Product")
                          where int.Parse(elem.Element("price").Value) < 54

                          let images = elem.Element("PopupImages").Elements("PopupImage")
                          let thumbs = elem.Element("ImageThumbs").Elements("thumb")

                          from img in images.Select(
                            (im, idx) => new KeyValuePair<string, string>(im.Value, thumbs.ElementAt(idx).Value)
                          )

                          select new
                          {
                              name = elem.Element("name").Value,
                              section = elem.Element("section").Value,
                              image = img.Key,
                              thumb = img.Value
                          };

Still in C#, but I think the idea is clear.

Upvotes: 0

Ahmad Mageed
Ahmad Mageed

Reputation: 96477

Try this approach:

Dim query = From product In doc.Elements("Product") 
            Where Integer.Parse(product.Element("price").Value) < 54 
            Select New With
            {
                .Name = product.Element("name").Value,
                .Section = product.Element("section").Value, 
                .Images = product.Descendants("PopupImage").Select(Function(i) i.Value), 
                .Thumbs = product.Descendants("thumb").Select(Function(t) t.Value) 
            }

For Each item in query
    Console.WriteLine(item.Name)
    Console.WriteLine(item.Section)
    Console.WriteLine("Images:")
    For Each image in item.Images
        Console.WriteLine("  " + image)
    Next
    Console.WriteLine("Thumbs:")
    For Each thumb in item.Thumbs
        Console.WriteLine("  " + thumb)
    Next
Next

If you really need a list just call query.ToList() and store the result in a variable or enclose the original query in parentheses and append ToList() (I prefer not to do this for readability). Similarly, the images and thumbnails are currently of type IEnumerable<string>, so if you need a list or array add the appropriate extension method call.

Upvotes: 1

Related Questions