LINQ Let Statement - Determine Null Elements


Does anybody know how to check for null elements in a Let statement?  In the code below, there are many Let statements inside the queries and i would like to code these to where it doesn't fail if element is missing.

For the element and attribute value checks I'm using an inline if statement to check for null first on the attribute or element object.  But this doesn't work for a Let statement using Elements("").FIrst()  

For example, I need to make sure the "sports-metadata" element exists before assigning to SportsMetaData.  Additionally, if the element doesn't exist, I'm not sure how that affects values that I try to assign values to using SportsMetaData.  Side note: In most cases, there is only one element with the name but I'm still using elements("  ").First(), not sure if that is wrong or not, it works.

 let SportsMetadata = listing.Elements("sports-metadata").First()

Any ideas? Thanks.
// This is needed when accessing an XElement or Attribute that has a namespace on its name
XNamespace xts = "";

List<Object> mySchedules = new List<object>();
XElement Root = xmlDoc.Root;
List<DocumentSportsInfo> docSportsInfo = 
    (from listing in xmlDoc.Descendants("sports-content")
     let SportsMetadata = listing.Elements("sports-metadata").First()
     let SportsContextCode = listing.Descendants("sports-content-code")
     select new DocumentSportsInfo
         //QueryDateTime = myFodHelper.ConvertFodDateTime(Root.Attribute("query-date-time").Value),
         QueryString = Root.Attribute("query-string").Value,
         HostName = Root.Attribute("hostname").Value,
         ResultCount = Convert.ToInt32(Root.Attribute("result-count").Value),
         ErrorCount = Convert.ToInt32(Root.Attribute("error-count").Value),
         TotalCount = Convert.ToInt32(Root.Attribute("total-count").Value),
         ElapsedTime = Root.Attribute("elapsed-time").Value,
         DocumentId = SportsMetadata.Attribute("doc-id").Value,
         //DocumentDateTime = myFodHelper.ConvertFodDateTime(SportsMetadata.Attribute("date-time").Value),
         DocumentClass = SportsMetadata.Attribute("document-class").Value,
         FixtureKey = SportsMetadata.Attribute("fixture-key").Value,
         RevisionId = SportsMetadata.Attribute("revision-id").Value,
         DocumentSportsTitle = SportsMetadata.Element("sports-title").Value,
         Priority = SportsContextCode
                    .Where(c => c.Attribute("code-type").Value == "priority")
                    .Select(c => c.Attribute("code-key").Value).FirstOrDefault(),
         Sport = SportsContextCode
                 .Where(c => c.Attribute("code-type").Value == "sport")
                 .Select(c => c.Attribute("code-key").Value).FirstOrDefault(),
         League = SportsContextCode
                 .Where(c => c.Attribute("code-type").Value == "league")
                 .Select(c => c.Attribute("code-key").Value).FirstOrDefault(),
         Conference = SportsContextCode
                 .Where(c => c.Attribute("code-type").Value == "conferenece")
                 .Select(c => c.Attribute("code-key").Value).FirstOrDefault(),
         TeamKeys = SportsContextCode
                 .Where(c => c.Attribute("code-type").Value == "team")
                 .Select(c => c.Attribute("code-key").Value).ToList(),


List<object> eventAndTeamInfo =
    (from sEvent in Root.Descendants("sports-event")
     select new List<object>
         new EventSportsInfo 
             Key = sEvent.Element("event-metadata").Attribute("event-key").Value, //event-status
             Status = sEvent.Element("event-metadata").Attribute("event-status").Value,
             Week = Int32.Parse(sEvent.Element("event-metadata").Element("event-metadata-american-football").Attribute(xts + "week").Value),
             // Add the others here
         new List<TeamsSportsInfo>  
            (from t in sEvent.Descendants("team")
            // The let's are here so that you do not have to write them 4 times and can
            // be used short string and can combine them as in FullName
            let FN = t.Element("team-metadata").Element("name").Attribute("first").Value
            let LN = t.Element("team-metadata").Element("name").Attribute("last").Value
            select new TeamsSportsInfo
                // Add fileds here
                Key = t.Element("team-metadata").Attribute("team-key").Value,                                                         
                FirstName = FN,
                LastName = LN,
                FullName = FN + " " + LN
                // Add more fileds here
     } as object


Open in new window

Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

You might try adding logic to either use the default if null or to skip null values. For example:

First().Where(p => p != null)

Additional resources on LINQ:

LINQ to SQL: .NET Language-Integrated Query for Relational Data:

LINQ extensions referenced in existing solution: 
"Microsoft has developed a set of extensions called Dynamic LINQ to do what you want to do. The extensions can be downloaded from this MS web site and I found the original link on this web site, "Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)""

LinqKit referenced in existing solution: 
"Have you looked at LinqKit ( or the work of Tomas Petricek ( Both places give good coverage of approaches to dynamic queries using Expression building. They both also provide some extensions (i.e. code) that make the job much easier. If I'm not mistaken, the tools in LinqKit are based on the work of Mr. Petricek, but are slightly newer and modified."

Let me know if you need additional information or which specific portion of the LINQ you are trying to protect from nulls.

Thanks, Eric

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
FYI... the .DefaultIfEmpty() only works to eliminate nulls if the default value is defined, not null, and an acceptable data value.
kruegersteAuthor Commented:
Basically, when looking at the query I provided, I want to make that query work no matter what the XML looks like.  

So, if we just look at the very first query for documentSportsInfo and break each of the areas down, we get....

1. from listing in xmlDoc.Descendants("sports-content")

I believe we are ok with this statement, if no sports-content tags exist then nothing is returned and I believe the query will not go any further.  If I'm mistaken, please let me know, haven't run into that yet.

2.      let SportsMetadata = listing.Elements("sports-metadata").First()
         let SportsContextCode = listing.Descendants("sports-content-code")

Again, I think (I maybe wrong and need this addressed too) we are ok with SportsContextCode since it uses Descendants, nothing will be returned if tag missing.

But SportsMetadata on the other hand will error out if this Element is missing.  I tried using this approach:

let SportsMetadata = listing.Elements("sports-metadata").SingleOrDefault()

and it works, SportsMetadata is set to null if element missing and it keeps going with no error, but I get an error on any values below that try to use SportsMetaData, even when checking for nulls with inline if statement as seen below in #3.  

3.      FixtureKey = (SportsMetadata.Attribute("fixture-key") == null) ? null : SportsMetadata.Attribute("fixture-key").Value,
         RevisionId = (SportsMetadata.Attribute("revision-id") == null) ? null : SportsMetadata.Attribute("revision-id").Value,

Next are assigning the values, lets stay with SportsMetadata as the example. These two statements above work fine if the attribute is missing in the element, it assigns null value if object is null.  But as stated in #2, if SportsMetadata is null, then these statements don't work anymore and a null reference error is thrown.  

Do I need to check every level of the structure for null values when assigning, doing a bunch of if statement nesting?  This seems like it could get real messy.  Anything better to use than if statements inline?  And these are simple examples, I have others where its Elements("").Elements("").Elements("").Attribute("").Value and this would be tough to check every level.  

There must be a best practice for each of these situations so the query doesn't break, I don't seem to understand the null value handling in LINQ very well yet.  

Any ideas?
Cloud Class® Course: Microsoft Azure 2017

Azure has a changed a lot since it was originally introduce by adding new services and features. Do you know everything you need to about Azure? This course will teach you about the Azure App Service, monitoring and application insights, DevOps, and Team Services.

1. Have you tested using a file without a "sports-content" section to verify it works? Should be a simple test

2. Try inserting a Where statement as below (add your criteria for a good record):

     let SportsMetadata = listing.Elements("sports-metadata").First()
     let SportsContextCode = listing.Descendants("sports-content-code")
     where SportsMetadata SportsMetadata.Attribute("fixture-key") != null && SportsMetadata.Attribute("revision-id") != null
     select new DocumentSportsInfo

3. See #2 above

Let me know if this helps at all.

Thanks, Eric
kruegersteAuthor Commented:
1. yeah, I will try this, it is less important to me right now since all of them do have this element.  It is more the Let statements and assigning values

2.  The Where statement won't really work because then it won't process the rest of the values if these conditions aren't meant.  If the fixture-key attribute is null, I still want to assign the other values like revision-id.  Each one needs to be independent of the other since I don't know between documents, which ones will be null.

The inline if statements seem to work just fine as-is, I'm more or less trying to figure out how to handle the Let keyword statements if they are null which will also not throw an error is used in assigning values.  I only brought the assigning values up because the one solution for the Let statements I found (SingleOrDefault) that works then breaks the inline if statements because I'm not checking the outer most parent object for nulls.  

kruegersteAuthor Commented:
Let me re-iterate so my question is more simple.

Take this Let statement:

let SportsMetadata = listing.Elements("sports-metadata").FistOrDefault()

I'm using the FirstOrDefault so if the element doesn't exist, it assigns null to SportsMetdata.  This works fine, if this is not the correct way to do this, please let me know.

Now take the next part where I use SportsMetadata to assign a value to a collection list property:

FixtureKey = (SportsMetadata.Attribute("fixture-key") == null) ? null : SportsMetadata.Attribute("fixture-key").Value,

Here I am checking if the attribute itself is null and assigning value.  Now if SportsMetadata is null then I get a null reference error thrown on SportsMetadata.

I can resolve this by changing the null check for both the attribute and the element by nesting the statements like this:

FixtureKey = (SportsMetadata == null) ? null : (SportsMetadata.Attribute("fixture-key") == null) ? null : SportsMetadata.Attribute("fixture-key").Value,

Now this works and all my issues are resolved but I want to know if there is a better way.  Because as I pointed out, there are some attributes down 3 levels and nesting even 2 inline if statements let alone 3,4,5 etc.... gets very messy and hard to understand.  

Just trying to find a better way.

Short of creating two lists to insert, one with null SportsMetadata and one with not null, and merging the lists, I'm not sure there is a better way.
kruegersteAuthor Commented:
Sorry, no help here.

Deleting this question and re-creating, need other input and won't get it with 7 comments on question already.  

Thanks for the attempt.
kruegersteAuthor Commented:
Close, thanks.
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
.NET Programming

From novice to tech pro — start learning today.