简体   繁体   中英

How do I select rows into columns?

Xml is as follows.

<System>
  <ID></ID>
  <Name></Name>
  <Monitor>
    <ID></ID>
    <Type></Type>
    <Alert>
      <ID></ID>
      <Status></Status>
    </Alert>
    <Alert>
      <ID></ID>
      <Status></Status>
    </Alert>
  </Monitor>
</System>
<System>
  <ID></ID>
  <Name></Name>
  <Monitor>
    <ID></ID>
    <Type></Type>
    <Alert>
      <ID></ID>
      <Status></Status>
    </Alert>
  </Monitor>
</System>

I want to traverse it like this

XElement xmlDoc = XElement.Load(@"xml");
var q = from el in xmlDoc.Elements("System") select el;

foreach(el in q) {
    Console.WriteLine(el.ID);
    Console.WriteLine(el.Name);

    if (el.Monitor) {
        foreach (mon in el.Monitor) {
            Console.WriteLine(el.ID);
            Console.WriteLine(el.Type);

            if (mon.Alert) {
                foreach (alert in mon.Alert) {
                    Console.WriteLine(alert.ID);
                    Console.WriteLine(alert.Status);
                }
            }
        }
    }
}

Currently I loop through each several times and use if to check field and then assign value to a variable. Then I have to loop through it again. Is there an easier way, and should I use plain LINQ or LINQ-TO-XML?

If you want to traverse it like that you can:

var xml = @"
<Root>
<System>
<ID>1</ID>
<Name>One</Name>
<Monitor>
    <ID>2</ID>
    <Type>Two</Type>
    <Alert>
    <ID>3</ID>
    <Status>Three</Status>
    </Alert>
    <Alert>
    <ID>4</ID>
    <Status>Four</Status>
    </Alert>
</Monitor>
</System>
<System>
<ID>5</ID>
<Name>Five</Name>
<Monitor>
    <ID>6</ID>
    <Type>Six</Type>
    <Alert>
    <ID>7</ID>
    <Status>Seven</Status>
    </Alert>
</Monitor>
</System>
</Root>
";

XElement xmlDoc = XElement.Parse(xml);
var q = xmlDoc.Elements("System");

foreach(var el in q) {
    Console.WriteLine(el.Element("ID").Value);
    Console.WriteLine(el.Element("Name").Value);

    foreach(var mon in el.Elements("Monitor")) {
        Console.WriteLine(mon.Element("ID").Value);
        Console.WriteLine(mon.Element("Type").Value);

        foreach(var alert in mon.Elements("Alert")) {
            Console.WriteLine(alert.Element("ID").Value);
            Console.WriteLine(alert.Element("Status").Value);
        }
    }
}

Ok try this code (see below code for explaination)

    class Program {
        static void Main(string[] args) {
            var xml = @"
<Root>
  <System>
    <ID>1</ID>
    <Name>one</Name>
    <Monitor>
      <ID>3</ID>
      <Type>t3</Type>
      <Alert>
        <ID>5</ID>
        <Status>a5</Status>
      </Alert>
      <Alert>
        <ID>6</ID>
        <Status>a6</Status>
      </Alert>
    </Monitor>
  </System>
  <System>
    <ID>2</ID>
    <Name>two</Name>
    <Monitor>
      <ID>4</ID>
      <Type>t4</Type>
      <Alert>
        <ID>7</ID>
        <Status>a7</Status>
      </Alert>
    </Monitor>
  </System>
</Root>
";
            XElement xmlDoc = XElement.Parse(xml);
            // set q to an enumeration of XElements
            // where the elements xname is "System"
            // the query actually executes the first time q is used
            var q = xmlDoc.Elements("System");
            foreach (var ele in q) {
                // Get the value of the Element with the xname of "ID"
                Console.WriteLine(ele.Element("ID").Value); 
                Console.WriteLine(ele.Element("Name").Value);
                // if ele.Elements("Monitor") returns nothing
                // then the foreach will be skipped (null-execution)
                foreach (var mon in ele.Elements("Monitor")) {
                    Console.WriteLine(mon.Element("ID").Value);
                    Console.WriteLine(mon.Element("Type").Value);
                    foreach (var alert in mon.Elements("Alert")) {
                        Console.WriteLine(alert.Element("ID").Value);
                        Console.WriteLine(alert.Element("Status").Value);
                        }
                    }
                }
            }
        }

This code will move through the XML document exactly once. In C# LINQ contains both language elements (like 'select' and 'from') and library element (.NET framework methods like XDocument.Elements); mixing the two is ok, but should only be done with understanding of what is occurring behind the statements. In this case you are asking for the XDocument to return all the child elements with an XName of "System". In the above code 'q' does not receive all of the elements, it receives an enumeration which can be iterated. The assignment of q is a very low cost operation because the XDocument contents are not transverse until the first foreach and then only one element at a time is examined. Do a search for "C# yield return" to see how this is implemented.

If you were only interested in the "Alert" elements you could do something like this:

var alerts = xmlDoc.Descendants("Alert")

This would return an enumeration of all elements with an XName of "Alert" (regardless of where they are in the hierarchy of the XML document). If you wanted to ensure the hierarchy you can use 'where', for example:

var alerts = xmlDoc.Descendants("Alert")
                   .Where(ele => (ele.Parent != null) && (ele.Parent.Name == "Monitor"))
                   .Where(ele => (ele.Parent.Parent != null) && (ele.Parent.Parent.Name == "System"));
foreach (var alert in alerts) {
    Console.WriteLine(alert.Element("ID").Value);
    Console.WriteLine(alert.Element("Status").Value);
    }

If you need to iterate over the same nodes multiple times you should consider converting the enumeration to a list or array, this saves time but increases memory usage. IEnumerable<> has extension methods ".ToArray()" and ".ToList()" for this purpose.

C# is an OOP language, I think you should harness that for this:

Example:

public class MySystem
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    public MyMonitor[] Monitors { get; private set; }

    public MySystem(XElement x)
    {
        Id = (int)x.Element("ID");
        Name = x.Element("Name").Value;
        // a little confusing from your code if there can be more than one Monitor
        Monitors = x.Elements("Monitor").Select(m => new MyMonitor(m)).ToArray();
    }
}

Do something similar for your Monitor class and your Alert class. I named it MySystem since a class named System is a mess.

You create your array of systems like I created the Monitor's array above with:

XElement xmlDoc = XElement.Load(@"xml");
MySystem[] systems = xmlDoc.Elements("System")
                           .Select(s => new MySystem(s))
                           .ToArray();

Now you have all your values in easy to use classes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM