Basic LINQ syntax

646 views Asked by At

Lets say you have an XML like like this:

 <data>
  <messages>
   <message name="Person" id="P">
    <field name="FirstName" required="Y" />
    <field name="LastName" required="Y" />
    <field name="Sex" required="N" />
   </message>
   <message name="Car" id="C">
    <field name="Make" required="Y" />
    <field name="Model" required="Y" />
    <field name="Year" required="N" />
   </message>
  </messages>
 </data>

Using Linq, how would you get a list of all required field names for Person?

I just started playing with LINQ/XML today this is about as far as Ive gotten.

    var q = from c in loaded.Descendants("field")
            where (string)c.Attribute("required") == "Y" &&
            // how to check the parent node (message) has an attribute (id="P")           
            select (string)c.Attribute("name");

    foreach (string name in q)
        Console.WriteLine(name);
4

There are 4 answers

1
John Farrell On BEST ANSWER

This answer is totally wrong considering the questioner changed his xml. Should I delete this.

var q = from c in loaded.Descendants("field")
            where (string)c.Attribute("required") == "Y" &&
                    c.Parent.Attribute("id").Value == "P"
            select (string)c.Attribute("name");

Adding the Xml I used because there is some confusion over the right solution.

XDocument loaded = XDocument.Parse(@"
<message name=""Person"" id=""P"">
    <field name=""FirstName"" required=""Y"" /> 
    <field name=""LastName"" required=""Y"" />
    <field name=""Sex"" required=""N"" />
    <message name=""Car"" id=""C"">
        <field name=""Make"" required=""Y"" />
        <field name=""Model"" required=""Y"" />
        <field name=""Year"" required=""N"" />
    </message>
</message>");
0
John Leidegren On

You use the Ancestors() method or Parent property as defined by the System.Xml.Linq.XNode and System.Xml.Linq.XObject

11
Matt On

you can remove the separate foreach loop and ugly cast by doing the following as well

(from c in myXML.Descendants("field")
         where c.Attribute("required").Value == "Y" && 
         c.Parent.Attribute("id").Value == "P" 
         select c.Attribute("name").Value).ToList().ForEach(s => Console.WriteLine(s.ToString()));

Edit:

as the problem with null or optional attributes cropped up, here is an example of how to add an extension method to handle null attributes, you can pass it a default to use if one doesnt exist (that is the second parameter in the extension method).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;

namespace MyTestApp
{

class Program
{
    static void Main(string[] args)
    {
        XDocument myXML = XDocument.Load(@"C:\file.xml"); 

        (from c in myXML.Descendants("field")
         where c.Attribute("required")
               .GetAttributeValueOrDefault("N") == "Y" && 
                c.Parent.Attribute("id").Value == "P"      
         select 
         c.Attribute("name").Value).ToList().ForEach(s => Console.WriteLine(s.ToString()));

        Console.ReadLine();
    }
}

public static class XLinqHelper
{
    // extension method that handles an xattribute and returns the provided default if the Xattrib is null
    public static string GetAttributeValueOrDefault(this XAttribute s, string defaultValue)
    {
        string retVal;
        if (s == null)
            retVal = defaultValue;
        else
            retVal = s.Value;
        return retVal;


    }
}

}

9
Guffa On

I added a root element and closing tag for the messages to make the XML valid:

XDocument loaded = XDocument.Parse(@"
  <messages>
    <message name=""Person"" id=""P"">
      <field name=""FirstName"" required=""Y"" />
      <field name=""LastName"" required=""Y"" />
      <field name=""Sex"" required=""N"" />
    </message>
    <message name=""Car"" id=""C"">
      <field name=""Make"" required=""Y"" />
      <field name=""Model"" required=""Y"" />
      <field name=""Year"" required=""N"" />
    </message>
  </messages>");

Instead of looking up all fields and then check the parent for each of them, look up the one parent that you are interrested in so that you have less fields to examine:

IEnumerable<string> fields =
  loaded.Root.Elements()
  .Where(m => m.Attribute("id").Value == "P")
  .Single()
  .Elements("field")
  .Where(f => f.Attribute("required").Value == "Y")
  .Select(f => f.Attribute("name").Value);

Edit:
Added the specifier "field" for the child elements in case the message element contains any other kinds of elements.

Edit 2:
I put together a working example with a subset of the actual data:

XDocument loaded = XDocument.Parse(@"
  <fix major=""4"" minor=""4"">
    <header>
    </header>
    <trailer>
    </trailer>
    <messages>
      <message name=""ResendRequest"" msgtype=""2"" msgcat=""admin"">
        <field name=""BeginSeqNo"" required=""Y"" />
        <field name=""EndSeqNo"" required=""Y"" />
      </message>
      <message name=""Reject"" msgtype=""3"" msgcat=""admin"">
        <field name=""RefSeqNum"" required=""Y"" />
        <field name=""RefTagID"" required=""N"" />
        <field name=""RefMsgType"" required=""N"" />
        <field name=""SessionRejectReason"" required=""N"" />
        <field name=""Text"" required=""N"" />
        <field name=""EncodedTextLen"" required=""N"" />
        <field name=""EncodedText"" required=""N"" />
      </message>
    </messages>
  </fix>");

IEnumerable<string> fields =
  loaded.Root.Element("messages").Elements("message")
  .Where(m => m.Attribute("name").Value == "Reject")
  .Single()
  .Elements("field")
  .Where(f => f.Attribute("required").Value == "Y")
  .Select(f=>f.Attribute("name").Value);