ausgeorge
ausgeorge

Reputation: 1015

LINQ to XML - list of custom objects with attributes from parent

The LINQ learning curve is proving too steep tonight, so I again come here for help. Lots of gratitude in advance.

Here's my current code;

_myList = (
from listLv2 in
    _documentRoot.Descendants("Level1").Descendants("Level2")
where
    (string)listLv2.Attribute("id") == "12345"

let attrib_Colour = listLv2.Attribute("Colour")
let attrib_ID = listLv2.Attribute("id")

//select listLv2.Descendants("Level3") <---- not working
select new MyObj
{
let attrib_ChildID = ???..Attribute("id")

ParentColour = attrib_Colour.Value, 
ParentID = attrib_ID.Value, // in this case 12345
ChildID = attrib_ChildID
}).ToList<MyObj>();

Here's what I'm trying to achieve;

  1. create a list of MyObjs. I convert Level3 elements in MyObjs.
  2. I only want the Level3 elements in the Level2 element with an ID of 12345
  3. I want to use some of the Attributes from the Level2(id=12345) element in each of the children Level3 elements

The XML structure is as follows;

<root>
  <Level1>
<Level2 id="12345" colour="Red">
  <Level3 id="0001" />
  <Level3 id="0002" />
  <Level3 id="0003" />
</Level2>
<Level2 id="45678" colour="Blue">
  <Level3 id="0004" />
  <Level3 id="0005" />
  <Level3 id="0006" />
</Level2>
</Level1>
</root>

The objects in the list should be like this;

MyObj.ParentID = 12345
MyObj.ParentColour = "Red"
MyObj.ID = 0001

MyObj.ParentID = 12345
MyObj.ParentColour = "Red"
MyObj.ID = 0002

MyObj.ParentID = 12345
MyObj.ParentColour = "Red"
MyObj.ID = 0003

The from and where are working. It selects 1 element, the Level2[id=12345]. Nice. The Level2 attributes are working.

Here's what I can't work out;

again, thanks

Upvotes: 2

Views: 886

Answers (3)

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236228

You should use SelectMany to get level3 items for your level2 element. Query syntax equivalent will be from listLv3 in listLv2.Descendants("Level3"):

from listLv2 in _documentRoot.Descendants("Level1").Descendants("Level2")
let attrib_ID = (string)listLv2.Attribute("id")
let attrib_Colour = (string)listLv2.Attribute("colour") // note: lowercase!
where attrib_ID == "12345"
from listLv3 in listLv2.Descendants("Level3")
select new MyObj
{
  ParentColour = attrib_Colour, 
  ParentID = attrib_ID, 
  ChildID = (string)listLv3.Attribute("id")
}

UPDATE: Why I mentioned SelectMany in solution? Because code above (if we'll forget about new range variables introduces with let keyword) will be compiled into following method-syntax query:

_documentRoot
    .Descendants("Level1")
    .Descendants("Level2")
    .Where(listLv2 => (string)listLv2.Attribute("id") == "12345")
    .SelectMany(listLv2 => listLv2.Descendants("Level3")) // flattening query
    .Select(listLv3 => new MyObj {
        ParentColour = (string)listLv3.Parent.Attribute("colour"),
        ParentID = "12345", // equal to id you are searching for
        ChildID = (string)listLv3.Attribute("id")
    });

And key point here is flattening query by selecting all level3 descendants from each matched level2 element into single sequence.

BTW Consider also XPath solution:

from listLv3 in root.XPathSelectElements("Level1/Level2[@id='12345']/Level3")
select new MyObj
{
   ParentColour = (string)listLv3.Parent.Attribute("colour"), 
   ParentID = "12345", 
   ChildID = (string)listLv3.Attribute("id")
}

Upvotes: 2

Anirudha
Anirudha

Reputation: 32797

_documentRoot.Elements("Level1")
             .Elements("Level2")
             .Where(x=>x.Attribute("id").Value=="12345")
             .Elements("Level3")
             .Select(y=>
                  new MyObj
                  {
                      ParentColour = y.Parent.Attribute("colour").Value, 
                      ParentID = y.Parent.Attribute("id").Value,
                      ChildID = (string)y.Attribute("id")
                  }
                 );

Upvotes: 0

AakashM
AakashM

Reputation: 63338

You only use select right at the end, once you've got to the point that the query expression is finished. Basically, you need to change your //select <-- not working to another from, and carry on. It will end up something like this:

_myList = (
from listLv2 in
    _documentRoot.Descendants("Level1").Descendants("Level2")
where
    (string)listLv2.Attribute("id") == "12345"

let attrib_Colour = listLv2.Attribute("Colour")
let attrib_ID = listLv2.Attribute("id")

from listLv3 in listLv2.Descendants("Level3") // <----  should work

select new MyObj
{
let attrib_ChildID = listLv3.Attribute("id")

ParentColour = attrib_Colour.Value, 
ParentID = attrib_ID.Value, // in this case 12345
ChildID = attrib_ChildID
}).ToList<MyObj>();

And note that your last ToList doesn't need an explicit type parameter specified, since C# can infer the correct type from the fact that the last thing you select are MyObjs.

Upvotes: 1

Related Questions