Search code examples
c#xmlxsdlinq-to-objects

How to get my inner elements in Linq2Objects?


I have a simple XML file here which I converted to XSD with xsd.exe and then converted to a strongly typed class with the /C on xsd.exe

<?xml version="1.0" encoding="utf-8" ?>
<MyStruct>
  <Test Name="Test1">
    <Params>
      <Param Name="Param1">I am param1</Param>
      <Param Name="Param2">I am param2</Param>
    </Params>
  </Test>
  <Test Name="Test2">
    <Params>
      <Param Name="Param1">I am param1</Param>
      <Param Name="Param2">I am param2</Param>
    </Params>
  </Test>
</MyStruct>

Now I read in the XML like so

 myStruct.ReadXml(file, XmlReadMode.ReadSchema);

Now I can check the properties off of myStruct and I can see Params and Test. Great!

Now using a simple link query I do this

 var tests = from s in myStruct.Test select s;

and sure enough I can see Test1 and Test2. Now understand then inside Test1 and Test2 there is Params. I notice I can do

 GetParamsRows();

and then I can do a foreach over it, each element is ParamsRow. And what is returns I can't seem to see "I am param1" or "I am param2".

Do I need an inner join or something?

I thought picking up a test has a collection or Params inside so i should be able to see it.

Edit

Now what I do notice is that in Test there is a test_id and in Params there is a Params_id and a Test_id.

It appears the relationship properties are setup but do I have to create the JOINS manual.

Test

I notice a property called Tables and in there are my 3 tables, I presume I need to do joins on these i.e from test to params to param. Like a database?

Am I on the right lines?


Solution

  • It would seem to be the case that you are actually not using LINQ to XML. What you have in this case is an XML-serializable set of classes generated by XSD.exe, and LINQ to Objects queries against those classes. The serializable classes use the older XML classes (XmlDocument, XmlElement, etc.) instead of the new ones (XDocument, XElement, etc.).

    If you are interested in using pure LINQ to XML, consider checking out the following tutorial, which I think you can adapt quickly to your needs:

    http://www.switchonthecode.com/tutorials/introduction-to-linq-simple-xml-parsing


    UPDATE: Non-LINQ-to-XML Solution

    What I believe is the immediate problem you face is that XSD.exe did not infer that you intended the <Test> element to contain at most one <Params> element. It is being treated as though there may be an array of <Params> elements.

    The ParamsRow type represents a single <Params> element; not the individual <Param> elements. I'd wager a pretty rock out of the garden that the ParamsRow object has a GetParamRows() method (note the singular).

    Another Update

    The example below performs the same operation as the LINQ-to-XML example. It iterates through each test and prints the test along with its parameters. For convenience, I use the SelectMany() extension method which "flattens" out the results into a single collection of ParamRow objects.

    var myStruct = new MyStruct();
    myStruct.ReadXml(@".\MyXml.xml", XmlReadMode.ReadSchema);
    foreach (MyStruct.TestRow test in myStruct.Test)
    {
        Console.WriteLine("Test #" + test.Name);
        foreach (var param in test.GetParamsRows().SelectMany(ps => ps.GetParamRows()))
        {
            Console.WriteLine("- {0}: {1}", param.Name, param.Param_Text);
        }
    }
    

    Check it out and let me know if that settles it.


    The following LINQ to XML example iterates through each Test element and presents its parameters on the console. Notice that the example does not use the classes generated with XSD.exe, and it does not require or employ the schema.

    var doc = XDocument.Load(@".\MyXml.xml");
    foreach (var test in doc.Descendants("Test"))
    {
        Console.WriteLine("Test #" + test.Attribute("Name").Value);
        foreach (var param in test.Descendants("Param"))
        {
            Console.WriteLine(
                "- {0}: {1}",
                param.Attribute("Name").Value,
                param.Value);
        }
    }
    

    The output looks like this:

    Test #Test1
    - Param1: I am param1
    - Param2: I am param2
    Test #Test2
    - Param1: I am param1
    - Param2: I am param2