Friday, October 28, 2011

Classes, Structs and LLBLGen Pro

I just found an interesting bug that has trickled into some of our code.

Can you identify what is wrong with the following code (don’t cheat by reading below):
Guid? enrollmentId = (from e in metaData.Enrollment
                      where e.LearnerId == learnerId 
                      select e.Id).FirstOrDefault();

The problem comes from the way that FirstOrDefault() operates. In LLBLGen's case, that method will either take the first value, or it will set the default value if it does not find a match. This is where it is important to know what you are selecting. In this case, the query is selecting the e.Id, which is a Guid. A Guid can never be null because it is a struct and it is not a class.
Other things that are structs include: bool, int, short, long, byte, DateTime, KeyValuePair, etc...
If you are selecting a value that is a class, the default value will be NULL because classes are reference types.
If you are selecting a value that is a struct, the default value will never be NULL because structs are value types.
Side Note: The default value for a struct can be determined generically using the following (Where T is a type of struct):
T defaultValueForType = default(T);
So how do we select a struct using FirstOrDefault()?
You can work around this by selecting a class instead. The best way to do it is to use an Anonymous Type as shown below.
var enrollmentId = (from e in metaData.Enrollment
                    where e.LearnerId == learnerId 
                    select new { e.Id }).FirstOrDefault();

Anonymous Types are classes, therefore they can be null. By using an anonymous type in our query, the enrollmentId variable will be NULL if there are no records found.

Links