3

Suppose I have the following XML file, essentially a random list of training courses consolidated from various sources:

<?xml version="1.0" encoding="utf-8"?>
<Courses>
  <Course>
    <Name>Big Data Advanced - Spark</Name>
    <Track>Big Data</Track>
    <Code>BD-102</Code>
  </Course>
  <Course>
    <Name>Big Data Advanced - YARN</Name>
    <Track>Big Data</Track>
    <Code>BD-102</Code>
  </Course>
  <Course>
    <Name>Big Data Basics</Name>
    <Track>Big Data</Track>
    <Code>BD-101</Code>
  </Course>
  <Course>
    <Name>DI Administration</Name>
    <Track>Data Integration</Track>
    <Code>DI-103</Code>
  </Course>
  <Course>
    <Name>DI Advanced</Name>
    <Track>Data Integration</Track>
    <Code>DI-102</Code>
  </Course>
  <Course>
    <Name>DI Basics</Name>
    <Track>Data Integration</Track>
    <Code>DI-101</Code>
  </Course>
</Courses>

I would like to group these courses by the value of their Track node, then sort by Code inside each track. The expected result is:

<?xml version="1.0" encoding="utf-8"?>
<Courses>
  <Track name="Big Data">
    <Course>
      <Name>Big Data Basics</Name>
      <Track>Big Data</Track>
      <Code>BD-101</Code>
    </Course>
    <Course>
      <Name>Big Data Advanced - Spark</Name>
      <Track>Big Data</Track>
      <Code>BD-102</Code>
    </Course>
    <Course>
      <Name>Big Data Advanced - YARN</Name>
      <Track>Big Data</Track>
      <Code>BD-102</Code>
    </Course>
  </Track>
  <Track name="Data Integration">
    <Course>
      <Name>DI Basics</Name>
      <Track>Data Integration</Track>
      <Code>DI-101</Code>
    </Course>
    <Course>
      <Name>DI Advanced</Name>
      <Track>Data Integration</Track>
      <Code>DI-102</Code>
    </Course>
    <Course>
      <Name>DI Administration</Name>
      <Track>Data Integration</Track>
      <Code>DI-103</Code>
    </Course>
  </Track>
</Courses>

I know how to achieve this result with XSLT transforms. However, I recently discovered the beauty of LINQ and would love to do the same with a single elegant query. I tried to consolidate an expression from the answers in How do I order a Group result, in Linq? and Group by in LINQ, but I do not understand fully the new part. Most of the time I end up with incorrect syntax that gives wrong or no results at all.

For example:

// Sort by Course Code and group by Track
var doc = XDocument.Load("all-courses.xml");
var query = doc.Root.Elements("Course")
    .GroupBy(c => c.Element("Track").Value)
    .Select(o => new { Track = o.Key, Courses = o.OrderBy(c => c.Element("Code").Value).ToList() }).ToList();

What I am missing/doing wrong?

Community
  • 1
  • 1
Laurent Vaylet
  • 377
  • 1
  • 2
  • 16

2 Answers2

2

new { ...} creates an anonymous type which is not what you want as an end result. In your case you want to transform the data back into XML. This means that you should create new XML objects out of the existing XML objects. Here is an example of how you can do that:

var doc = XDocument.Load("all-courses.xml");

var elements = doc.Root.Elements("Course")
    .GroupBy(c => c.Element("Track").Value)
    .Select(o =>
        new XElement(
            "Track",
            o.OrderBy(c => c.Element("Code").Value),
            new XAttribute("name", o.Key)));

var new_doc = new XDocument(new XElement("Courses", elements));

new_doc.Save("result.xml");
Yacoub Massad
  • 27,509
  • 2
  • 36
  • 62
2

Agree with the solution of @YacoubMassad, I just want to add you can also pass the attribute and the nested elements in the constructor of Track element:

var query = xDoc.Root.Elements("Course")
                     .GroupBy(c => c.Element("Track").Value)
                     .Select(g=>new XElement("Track",new XAttribute("name",g.Key),g.OrderBy(c => c.Element("Code").Value))); 

xDoc.Root.ReplaceNodes(query);
ocuenca
  • 38,548
  • 11
  • 89
  • 102
  • Thanks @YacoubMassad and @octavioccl. I voted for both of you and finally picked Octavio's answer as it is more concise and performs the operations in place. Anyway, @YacoubMassad, I loved your explanation of the `new` keyword. – Laurent Vaylet Feb 18 '16 at 15:28