2

I have the following XML

<?xml version="1.0" encoding="utf-8"?>
<Applications   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Blocks>
    <Block Name="block1">
        <Attributes>
            <Tag>Attribute1</Tag>
            <Layer>layer1</Layer>
        </Attributes>
        <Attributes>
            <Tag>Attribute2</Tag>
            <Layer>layer2</Layer>
        </Attributes>
    </Block>
    <Block Name="block2">
        <Attributes>
            <Tag>Attribute1</Tag>
            <Layer>layer0</Layer>
        </Attributes>
    </Block>
</Blocks>
</Applications>

I would like to use a linq statement to catch all the details and populate a List with the following class. i.e. List

public class Block
{    
public string Tag { get; set; }
    public string Layer { get; set; }
}

I've tried...

List<Block> data =
(from a in xdoc.Root.Elements("Blocks")
where (string)a.Attribute("Name") == "block1"
select new Block
{
    Tag = (string)a.Element("Tag"),
    Layer = (string)a.Element("Layer")
}).ToList();

Can you see where I'm going wrong, little new to linq.

4 Answers4

3

Try:

LAMBDA Syntax:

xdoc.Root.Elements("Blocks").Elements("Block")
    .Where(w => (string)w.Attribute("Name") == "block1")
    .Elements("Attributes")
    .Select(s => new Block
    {
        Tag = (string)s.Element("Tag"),
        Layer = (string)s.Element("Layer")
    });

If you want to use query syntax:

from a in (from b in xdoc.Root.Elements("Blocks").Elements("Block")
        where (string)b.Attribute("Name") == "block1"
        select b).Elements("Attributes")
        select  new Block
        {
            Tag = (string)a.Element("Tag"),
            Layer = (string)a.Element("Layer")
        };
mnsr
  • 12,337
  • 4
  • 53
  • 79
2

According to your xml document, I suggest you to change your class like this:

public class Block
{    
   public string Name { get; set; }
   public List<BlockAttribute> Attributes { get; set; }
} 
public class BlockAttribute 
{  
   public string Tag { get; set; }
   public string Layer { get; set; }
}

Then use this code:

var blocks = (from b in xdoc.Descendants("Block")
                 select new Block {
                          Name = (string)b.Attribute("Name"),
                          Attributes = (from a in b.Elements("Attributes")
                                           select new BlockAttribute {
                                                   Tag = (string)a.Element("Tag"),
                                                   Layer = (string)a.Element("Layer")
                                                  }).ToList()
                                    }).ToList();
Selman Genç
  • 100,147
  • 13
  • 119
  • 184
1

You're almost there.

Fix your linq statement a bit:

List<Block> data = (from a in (from b in xdoc.Root.Elements("Blocks").Elements("Block")
                               where b.Attribute("Name").Value.Equals("block1")
                               select b).Elements("Attributes")
                    select new Block()
                    {
                         Tag = a.Element("Attributes").Element("Tag").Value,
                         Layer = a.Element("Attributes").Element("Layer").Value
                    }).ToList();

Also make sure your XML is valid since you're mixing cases. Additionally as per @Grant-Winney mentioned your application tag is still open in your sample.

JNYRanger
  • 6,829
  • 12
  • 53
  • 81
  • The query still doesn't return any results? – user1641194 Jan 17 '14 at 03:23
  • If you're using namespaces in your xml you may need to add them into the queries using a `XNamespace` object. See http://stackoverflow.com/questions/2340411/use-linq-to-xml-with-xml-namespaces – JNYRanger Jan 17 '14 at 03:31
  • Ok, the query is working but it only returns one attribute value, yet block1 has 2 attributes – user1641194 Jan 17 '14 at 03:34
  • @user1641194 you're better off using a lambda solution for this. It's just simpler. The above solution will only pick the first element named 'Attributes'. It needs to drill further into the XElement before you get what you're after. – mnsr Jan 17 '14 at 03:42
  • Can your `block` class take multiple attributes, or do you want two `block` objects created for each block element within your XML? – JNYRanger Jan 17 '14 at 03:44
  • @jzm I posted a non-lambda solution to keep it simple since OP said they were new to linq. The lambda ones are a whole lot nicer to look at and work with though. – JNYRanger Jan 17 '14 at 03:45
  • I need to cater for multiple attributes. Also the lambda solution didn't seem to use any *where* statement. I did like the simplicity – user1641194 Jan 17 '14 at 03:48
  • @JNYRanger I know. I've updated my answer to further help with a query syntax version. – mnsr Jan 17 '14 at 03:49
  • @user1641194 see my answer (below) – mnsr Jan 17 '14 at 03:49
  • @user1641194 jzm 's solution below shows the where statement. However, you still can't have multiple "attributes" within objects of the block class you posted in your sample, only a single Tag and single Layer property. – JNYRanger Jan 17 '14 at 03:52
  • @JNYRanger You can. I just tested this in LINQPad and both items were returned. Whereas yours is only returning the first value. Your answer needs to pull out 'Elements' (IEnumerable), not just "Element" (Singular) – mnsr Jan 17 '14 at 03:54
  • @jzm ah I see what you're saying. It needed an inner query, doh to query syntax. I'll fix it. – JNYRanger Jan 17 '14 at 03:57
  • @JNYRanger Exactly why i said lambda is cleaner in this case :) – mnsr Jan 17 '14 at 03:59
  • @jzm Lambdas are always cleaner, +1 and touche sir. However, if OP still wanted to have the potential for multiple "attribute" properties in a single block object like their XML kind of hints to, then they are still going to have a few issues. – JNYRanger Jan 17 '14 at 04:04
0

This should do the job.

        var blocks = xdoc.Descendants("Attributes").Select(x => new Block
            {
                Tag = x.Descendants("Tag").Single().Value,
                Layer = x.Descendants("Layer").Single().Value
            }).ToList();
Rezo Megrelidze
  • 2,877
  • 1
  • 26
  • 29
  • Lambdas are usually tricky to new linq folks since most people are already familiar with SQL-like queries. Although this would work in this case, it's not very forgiving depending on the XML. – JNYRanger Jan 17 '14 at 03:21