1

I have xml code that would be something like this: file named: player.xml

<root>
  <person>
    <fname>Dwight</fname>
    <lname>Howard</lname>
    <vertLeap>
      <try1>32.33</try1>
      <try2>33.33</try2>
      <try3>34.33</try3> 
    </vertLeap>
  </person>
  <person>
    <fname></fname>
    <lname>Jordan</lname>
    <vertLeap>
      <try1>40.33</try1> 
    </vertLeap>
  </person>
</root>

This isn't my real xml, but should work for the example. Now I want to use linq to xml to read the data. Iam am trying like this.

Class:

public class Player
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Leap1 { get; set; }
    public int Leap2 { get; set; }
    public int Leap3 { get; set; }

    public WritePlayertoDatabase()
    {
        //do stuff to write
    }
}

Query:

XDocument xDoc = XDocument.Load("player.xml");
var player_query = from p xDoc.Desendants("person")
                   select new Player
                   {
                       FirstName = p.Element("fname"),
                       LastName = p.Element("lname"),
                       Leap1 = p.Element("try1"),
                       Leap2 = p.Element("try2"),
                       Leap3 = p.Element("try3")
                   };

I'm getting a NullReferenceException. Is there a way to test if the elements exist before I try to use the value? Or is there a way better way to accomplish this?

abatishchev
  • 92,232
  • 78
  • 284
  • 421
Chris
  • 108
  • 1
  • 9

3 Answers3

4

So there are a few things wrong with your linq query.

1) p.Element("fname") will return the fname XML Element, not a string. So you still need to get the element's value. Similarly, the Leap1-3 properties are int, but you will get the element value as a stirng and need to convert it. But, try1-3 are not ints in the xml, so you probably want to change the type to somehting else in the Player class.

2) try1 - tryx element s are all children of 'vertleap'. You can't directly get element 'try1' from 'person'. It will be null.

So how about something more like this:

    var player_query = from p in xDoc.Descendants("person")
                       select new Player
                       {
                           FirstName =  p.Element("fname") != null ? p.Element("fname").Value : "",
                           LastName = p.Element("lname") != null ? p.Element("lname").Value : "",
                           Leap1 = p.Element("vertLeap") != null ? (p.Element("vertLeap").Element("try1") != null ? Decimal.Parse(p.Element("vertLeap").Element("try1").Value) : 0) : 0,

                           Leap2 = p.Element("vertLeap") != null ? (p.Element("vertLeap").Element("try2") != null ? Decimal.Parse(p.Element("vertLeap").Element("try2").Value) : 0) : 0,
                           Leap3 = p.Element("vertLeap") != null ? (p.Element("vertLeap").Element("try3") != null ? Decimal.Parse(p.Element("vertLeap").Element("try3").Value) : 0) : 0,
                       };
Simon C
  • 9,008
  • 3
  • 33
  • 53
  • SimonC - thanks for the quick reply. That looks like what I wanted to do, but I couldn't come up with that. I'll check it tomorrow at work before I mark as solved. I just wanted say thanks! – Chris Feb 15 '13 at 00:44
  • Worked like a charm! Thanks again! – Chris Feb 15 '13 at 15:02
0

Using the XML you posted, the p.Element("try<X>") calls will ALWAYS return nulll... as I see @SimonC has just pointed out. You'll need to tunnel down the XML tree to get the value out, or use Descendants("try<x>").FirstOrDefault() to get the first descendant with a matching name. This may still be null, which leads us back to the real point of the question.

The issue really is that you are trying to perform an operation on an object which may not exist. Apart from the conditional sequence that @SimonC suggested, you can use helper methods or extensions to detect a missing element and provide a meaningful default value.

public static string AsString(this XElement self)
{
    if (self == null)
        return null;
    return self.Value;
}

public static double AsDouble(this XElement self, double defValue = default(double))
{
    if (self == null)
        return defValue;
    double res = defValue;
    try { res = (double)self; }
    catch { }
    return res;
}

public static int AsInt(this XElement self, int defValue = default(int))
{
    if (self == null)
        return defValue;
    double res = defValue;
    try { res = (double)self; }
    catch { }
    return (int)res;
}

Then your Linq query becomes:

var player_query = from p in xDoc.Descendants("person")
    select new Player
    {
        FirstName = p.Element("fname").AsString(),
        LastName = p.Element("lname").AsString()
        Leap1 = p.Descendants("try1").FirstOrDefault().AsInt(),
        Leap2 = p.Descendants("try2").FirstOrDefault().AsInt(),
        Leap3 = p.Descendants("try3").FirstOrDefault().AsInt()
    };

If there is no 'try1' node within the 'player' node's descendants, the FirstOrDefault() method will return null. The AsInt() extension method is then called with a null this reference, which it detects and returns default(int) instead of throwing an exception.

You could also write a bunch of ConvertElementToXXX(elem, defValue) methods, but I think this is a reasonably valid use of extensions. It's just a shame that XElement doesn't implement IConvertible.

Corey
  • 13,462
  • 1
  • 29
  • 60
0

I know I went overboard with this quesiton, but after fiddling around with it I decided to re-write your example with XmlSerialization instead. Depending on your needs, I would highly recommend you serialize your data into objects, and pass these around.

You will need the using using System.Xml.Serialization; namespace, but I wrote the below complete program if you want to use any of it.

public class Program
{
    [XmlRoot("root")]
    public class Team
    {
        private List<Player> players = new List<Player>();

        [XmlElement("player")]
        public List<Player> Players { get { return this.players; } set { this.players = value; } }

        // serializer requires a parameterless constructor class
        public Team() { }
    }

    public class Player
    {
        private List<int> verticalLeaps = new List<int>();

        [XmlElement]
        public string FirstName { get; set; }
        [XmlElement]
        public string LastName { get; set; }
        [XmlElement]
        public List<int> vertLeap { get { return this.verticalLeaps; } set { this.verticalLeaps = value; } }

        // serializer requires a parameterless constructor class
        public Player() { }
    }

    static public void Main(string[] args)
    {
        Program myProgram = new Program();
        myProgram.WritePlayertoDatabase();
        myProgram.ReadPlayerDatabase();
    }

    public void WritePlayertoDatabase()
    {
        Player p1 = new Player() { FirstName = "dwight", LastName = "howard", vertLeap = new List<int>() { 1, 2, 3 } };
        Player p2 = new Player() { FirstName = "dwight", LastName = "howard", vertLeap = new List<int>() { 1 } };

        Team players = new Team();
        players.Players.Add(p1);
        players.Players.Add(p2);
        XmlSerializer serializer = new XmlSerializer(typeof(Team));
        using (TextWriter textWriter = new StreamWriter(@"C:\temp\temp.txt"))
        {
            serializer.Serialize(textWriter, players);
            textWriter.Close();
        }
    }

    public void ReadPlayerDatabase()
    {
        Team myTeamData = new Team();
        XmlSerializer deserializer = new XmlSerializer(typeof(Team));
        using (TextReader textReader = new StreamReader(@"C:\temp\temp.txt"))
        {
            myTeamData = (Team)deserializer.Deserialize(textReader);
            textReader.Close();
        }
    }
}
Magnum
  • 1,475
  • 4
  • 18
  • 38
  • Thanks a lot Magnum! I will soon test that out and try to gain a better understanding of serialization. – Chris Feb 15 '13 at 15:09