0

Given the following XML snippet, is there a way to both query and populate a class object in one LINQ statement? It's confusing because of the need to select using attribute values.

<data>
 <array>
  <item key="0">
   <map>
    <item key="mrid">53030</item>
    <item key="mrtitle">GeneralFeedback</item>
   </map>
  </item>
 </array>
</data>

Class:

public class Incident
{
    public int ID { get; set; }
    public string Title { get; set; }
}

Current (working) code (where result is the XML snippet as a string):

var data = XDocument.Parse(result);

var id = from item in data.Descendants("item")
    where item.Attribute("key").Value == "mrid"
    select item.Value;

var title = from item in data.Descendants("item")
    where item.Attribute("key").Value == "mrtitle"
    select item.Value;

var incident = new Incident
{
    ID = Convert.ToInt32(id.FirstOrDefault()),
    Title = title.FirstOrDefault()
};

Based on the answers given I learned some useful things and came up with this variation:

var incidents = data.Descendants("map")
    .Select(i => i.Descendants("item")
        .ToDictionary(m => m.Attribute("key").Value, m => m.Value))
    .Where(i => i.ContainsKey("mrid")
                && i.ContainsKey("mrtitle"))
    .Select(i => new Incident
    {
        ID = int.Parse(i["mrid"]),
        Title = i["mrtitle"]
    });

One thing I really like is that this creates an IEnumerable that allows for multiple incidents being present in the XML data.

Stonetip
  • 1,150
  • 10
  • 20

3 Answers3

1

Check out this post to learn how to convert your XML schema to a C# class Generate C# class from XML

Then you can use your new type and de-serialize your XML to a class

XmlSerializer serializer = new XmlSerializer(typeof(Incident));
using (StringReader reader = new StringReader(xmlDocumentText))
{
    Incident incident= (Incident)(serializer.Deserialize(reader));
}
Community
  • 1
  • 1
  • Interesting. I tried it, but ran into errors. Tried a couple of things to overcome that, but same error ({" was not expected)...which is weird since none is being used. – Stonetip May 13 '15 at 01:57
1

is there a way to both query and populate a class object in one LINQ statement?

Yes, well sorta ... and it remains quite ugly. The below "single" multi-step LINQ statement ensures only the items that belong to the same map element get selected. Like your code sample, it will blow up in your face if the items with the required key values are missing (or the "mrid" element is not an int).

var key_vals = new List<string> { "mrid", "mrtitle" };
var xdoc = XDocument.Load(@"c:\temp\test.xml");
var incidents = xdoc.Descendants("map").Select(map => {
    var items = map.Descendants("item").Where(i => key_vals.Contains(i.Attribute("key").Value));
    var idItem = items.Where(x => x.Attribute("key").Value == "mrid").First();
    var titleItem = items.Where(x => x.Attribute("key").Value == "mrtitle").First();
    return new Incident {
        ID = int.Parse(idItem.Value),
        Title = titleItem.Value
    };
});

foreach (var i in incidents)
    Console.WriteLine("ID = {0}, Title = {1}", i.ID, i.Title);

It will produce the output below for your given xml input file:

ID = 53030, Title = GeneralFeedback
Alex
  • 13,024
  • 33
  • 62
  • I find this useful. It's more specific and shows me how to return a class object from within a LINQ query. – Stonetip May 13 '15 at 02:02
  • In this case, Where not necessary: var idItem = items.First(x => x.Attribute("key").Value == "mrid"); Link: http://rextester.com/WBMWS91290 – FoggyFinder May 13 '15 at 05:03
1

Alex has already given a perfect answer, but I find this a little more readable (: The Where clause ensures each item found, has the keys required to construct an Incident.

var incidents = xdoc.Root
                    .Element("array")
                    .Elements("item")
                    .Select(i => i.Element("map")
                                  .Elements("item")
                                  .ToDictionary(m => m.Attribute("key").Value,
                                                m => m.Value))
                    .Where(i => i.ContainsKey("mrid")
                             && i.ContainsKey("mrtitle"))
                    .Select(i => new Incident
                                 {
                                     ID = int.Parse(i["mrid"]),
                                     Title = i["mrtitle"]
                                 });
Sam Nelson
  • 387
  • 1
  • 11
  • I selected @Alex's answer because it pointed me in the right direction. But combined with this, I better understand what can be done. I especially appreciate the emphasis on ensuring each item is present. I added a variation to my question based on both answers. Thanks. – Stonetip May 13 '15 at 14:52