locked
Handle null values in XML to LINQ queries RRS feed

  • Question

  • Hi,

    I have an xml, in which some tags are optional so sometimes they are present and sometimes they are not.  Now when I write a linq to xml query, how do I handle such tags, which return null values and so any operation on them leads to an exception. e.g. consider the following query I am writing

     

    Code Snippet

    var pInfo = from package in packages.Elements()

                select new

                {

                Name = package.Element( "name").Value,

                Version = package.Element( "version").Value,

                }

     

     

     

    The above code queries an xml in which the version tag is optional, so whenever version is not available the call to

    package.Element("version").Value fails with a null exception.

     

    How do I handle such cases without resorting to doing it manually using foreach?

     

    Thanks in advance.

    Thursday, January 15, 2009 1:51 AM

Answers

  • Well there is quite a difference between doing

    package.Element("name").Value

    and

    (string)package.Element("name")

    as the former gives an exception when the 'name' element does not exist while the latter does not give an exception. It yields null however if the 'name' element does not exist.

    You do not seem to want the null value but rather want to assign an empty string if the element does not exist. In that case the code you consider a workaround is fine in my view.

    If you want something shorter then use my approach and the ?? operator as follows:

     

    Code Snippet

                XElement packages = XElement.Parse(@"<packages>
      <package>
        <name>p 1</name>
      </package>
      <package>
        <name>p 2</name>
        <version>1.0</version>
      </package>
    </packages>");
                var query = from package in packages.Elements()
                            select new {
                                Name = (string)package.Element("name")?? "",
                                Version = (string)package.Element("version")?? ""
                            };
                foreach (var package in query)
                {
                    Console.WriteLine("Name: \"{0}\"; Version:\"{1}\".", package.Name, package.Version);
                }

     

     

     

    Thursday, January 15, 2009 3:58 PM

All replies

  • There are two ways to so it. Either  to use the DefaultOrEmpty()  method or use Any() method.

     

    Refer:

     

    http://naspinski.net/post/Using-DefaultIfEmpty-to-check-if-an-element-exists-with-LINQ.aspx

     

    Thursday, January 15, 2009 5:52 AM
  • Use

    Code Snippet

    var pInfo = from package in packages.Elements()

                select new

                {

                Name = (string)package.Element( "name"),

                Version = (string)package.Element( "version"),

                }

     

     

    that way the Name and Version property will be null if the name respectively version element does not exist in the XML. That is possible because of this http://msdn.microsoft.com/en-us/library/bb155263.aspx conversion operator.

    Thursday, January 15, 2009 1:13 PM
  • Hi, Thts not exactly what i want, the problem is that even in the above case
    Name = (string)package.Element( "name") will return a null. But as you can see that in my query I want the value of this tag not the tag. So in any case exception will be raised.
    I found a solution though, But its not that great just a workaround

    Code Snippet

    var            }

     pInfo = from package in packages.Elements()

                select new

                {

                Name = package.Element( "name")==null?"": package.Element( "name").Value,

                Version = package.Element( "version")==null?"":

    package.Element( "version").Value,


    As you can see the code is kinda ugly. I would like to know if there's a nicer way to do this, I am sure it must be there because this is such a common problem.


    Thursday, January 15, 2009 3:25 PM
  • Well there is quite a difference between doing

    package.Element("name").Value

    and

    (string)package.Element("name")

    as the former gives an exception when the 'name' element does not exist while the latter does not give an exception. It yields null however if the 'name' element does not exist.

    You do not seem to want the null value but rather want to assign an empty string if the element does not exist. In that case the code you consider a workaround is fine in my view.

    If you want something shorter then use my approach and the ?? operator as follows:

     

    Code Snippet

                XElement packages = XElement.Parse(@"<packages>
      <package>
        <name>p 1</name>
      </package>
      <package>
        <name>p 2</name>
        <version>1.0</version>
      </package>
    </packages>");
                var query = from package in packages.Elements()
                            select new {
                                Name = (string)package.Element("name")?? "",
                                Version = (string)package.Element("version")?? ""
                            };
                foreach (var package in query)
                {
                    Console.WriteLine("Name: \"{0}\"; Version:\"{1}\".", package.Name, package.Version);
                }

     

     

     

    Thursday, January 15, 2009 3:58 PM
  • Hi I'm currently working on some project like this, I just wanna ask about performance, because I have to handle large XML files (60 - 150 MB). And them some times are missing of some elements. I think the ?? operator is the best way to handle it.

    Cesar
    Dev: C#, Windows CE, Delphi, Visual Foxpro
    DB: SQL Server, MS Access
    Monday, January 19, 2009 9:59 PM
  • somewhat related newbie question here:

    whats the syntax for a case like:

    Name = (string) f.Element("ParentElement").Element("ChildElement")

    when the ParentElement is null

    If I try like this:

    Name = (string) f.Element("ParentElement").Element("ChildElement") ?? ""

    it still throws null exception
    Sunday, February 8, 2009 11:10 AM
  • ok, i figured it out...not sure its the best way but here is how i got it to work:

    Name= f.Element("ParentElement") != null ? (string) f.Element("ParentElement").Element("ChildElement") : null
    Sunday, February 8, 2009 11:45 AM
  • Hi Joe,
    Were you able to find an optimal solution for your problem? I have the following XML data that I need to parse, and im having the same problem you are with extracting some of the elements and their attributes:

          <player>
            <player-metadata player-key="l.ncaa.org.mbasket-p.24398" status="bench">
              <name first="Garrett" last="Kissel"/>
            </player-metadata>
            <player-stats time-played-total="4">
              <penalty-stats count="0" type="personal fouls"/>
              <penalty-stats count="0" type="technical fouls"/>
              <outcome-totals points-scored-for="0"/>
              <player-stats-basketball minutes-played="4:">
                <stats-basketball-offensive field-goals-made="0" field-goals-attempted="3" three-pointers-made="0" three-pointers-attempted="0" free-throws-made="0" free-throws-attempted="0" assists-total="0" points-scored-total="0" turnovers-total="0"/>
                <stats-basketball-rebounding rebounds-offensive="1" rebounds-defensive="0" rebounds-total="1"/>
                <stats-basketball-defensive steals-total="0" blocks-total="0"/>
              </player-stats-basketball>
            </player-stats>
          </player>
          <player>
            <player-metadata player-key="l.ncaa.org.mbasket-p.16172" status="scratched">
              <name first="Mike" last="Trimboli"/>
            </player-metadata>
            <player-stats>
              <outcome-totals/>
            </player-stats>
          </player>

    As you can see from the code below:
            var boxScorePlayer = from team in xmlContent.Descendants("team")
                                 select new
                                 {
                                     TeamKey = (string)team.Element("team-metadata").Attribute("team-key"),
                                     TeamName = (string)team.Element("team-metadata").Element("name").Attribute("first") + " " +
                                                (string)team.Element("team-metadata").Element("name").Attribute("last"),
                                     PlayerStats = from boxScoreContent in team.Descendants("player")
                                                   let PFouls = boxScoreContent.Element("player-metadata").Elements("penalty-stats").Where(s=>(string)s.Attribute("type")=="personal fouls").First()
                                                   select new
                                                   {
                                                       PlayerKey = boxScoreContent.Element("player-metadata").Attribute("player-key").Value,
                                                       Status = boxScoreContent.Element("player-metadata").Attribute("status").Value,
                                                       PlayerName = boxScoreContent.Element("player-metadata").Element("name").Attribute("first").Value + " " +
                                                                    boxScoreContent.Element("player-metadata").Element("name").Attribute("last").Value,
                                                       TimePlayedTotal = (int?)boxScoreContent.Element("player-stats").Attribute("time-played-total") ?? 0,
                                                       PersonalFouls = (int?)PFouls.Attribute("count") ?? 0
                                                       FieldGoalsMade = (int?)boxScoreContent.Element("player-stats").Element("player-stats-basketball").Element("stats-basketball-offensive").Attribute("field-goals-made") ?? 0
                                                   }
                                 };

    My problem is that in the XML above, the second set of data doesnt have a <penalty-stats> element, hence I cant get to the  'personal-fouls' attribute 
    and I believe that my LET statement fails. Also, I cant seem to extract the 'field-goals-made' attribute because it doesnt exist in the second
    Player as well, so it fails trying to get FieldGoalsMade.
    Does anyone know how I can extract this, and if it doesnt exist, just return a default value of zero?
    Thanks

    You are so wise...like a miniature budha covered in fur. -Anchorman
    Monday, March 9, 2009 2:25 PM