Reputation: 598
I'm using LINQ to Entities to get some data from a database. Below is my query.
var location = from l in dbContext.Locations
join e in dbContext.Equipment on l.ID equals e.LocationID into rs1
from e in rs1.DefaultIfEmpty()
where ids.Contains(l.ID)
select new
{
EquipmentClass = e,
LocationID = l.ID,
LocationName = l.Name,
EquipmentName = e == null ? null : e.Name,
Description = e == null ? null : e.Description,
InServiceStatus = e == null ? false : e.InServiceStatus,
EquipmentType = e.EquipmentType.Name
};
foreach (var item in location)
{
// some logic
}
In the code above, ids
is a list of ints that I pass in to filter the results. When I get the results, I see that one of the return records has a null EquipmentClass
. I had performed some null checking but realized that I forgot to do null checking on one of the properties. Now I would expect to get a null reference exception on EquipmentType = e.EquipmentType.Name
but I don't. To my surprise, it works just fine, and is set to null. My EquipmentClass
has a property type of EquipmentType
, which is another class. EquipmentType
has a Name
property which is a String.
Just as a test, I removed the null check from InServiceStatus = e == null ? false : e.InServiceStatus
and it fails with an invalid operation exception upon running a foreach loop using the query.
Update:
foreach (var item in location)
{
var p = item.EquipmentClass.EquipmentType.Name;
}
Added this right after the query. On the assignment of p, I get a null reference exception. I'm not sure how it even gets that far, as it should fail on the first line of the foreach loop. Without the line declaring variable p, I do not get a null reference exception. If anyone can explain what is happening I would be grateful. Just for reference, the values of item.EquipmentClass
and item.EquipmentType
are both null by the time the foreach loop starts.
Update2: I found this link where it seems that someone has an almost identical issue using LINQ to SQL. I get the gist of the answer but don't fully understand its potential impact on my two questions above.
Upvotes: 3
Views: 504
Reputation: 627
your e.EquipmentType.Name
is null
and it is getting assigned to EquipmentType
and it is perfectly fine to assign null
to a nullable
type and you are using DefaultIfEmpty()
which will initialize the elements with their default value if it fails to match any condition and in this case your e.ElementType.Name
is getting set to null
which is fine I guess. use ToList()
that will throw the exception.
I hope I am making some sense and you people might have discussed this.
Upvotes: -1
Reputation: 5791
If you write a LINQ query against IQueryable
, what happens is that the methods that you are calling behind the scenes (such as Select
, Where
, etc.) don't do anything more than just recording how you have called them, i.e. they record the predicate expressions and carry over a LINQ provider. As soon as you start iterating the query, the provider is asked to execute the query model. So basically, the provider uses the expression model to give you a result of the expected type.
The provider is by no means required to actually compile or even execute the code (model) you delivered as an expression. In fact, the whole point of LINQ to SQL or LINQ to Entities is that the provider does not do this and translate the code expression to SQL instead.
Therefore, your query is actually rendered as an SQL query and the result of that query is translated back. Therefore, the variable e
that you see in the query is not necessarily really created but only used for the LINQ provider to compile an SQL query. However, most database servers have null propagations.
Run the same query against LINQ to Objects and you will get your missing NullReferenceException.
Upvotes: 6
Reputation: 25965
Your update helped me understand your real concern. The concept you need to know in regards to LINQ queries is deferred execution in LINQ.
Please go through below links for more details:
What are the benefits of a Deferred Execution in LINQ?
Linq - What is the quickest way to find out deferred execution or not?
Now what happens in your case? You've stored your query in location
variable. That particular step is just the initialization part. It doesn't really executes the query on your database through your ORM layer. This is how you can test this.
Put a break point on the line of code where you're initializing location
variable with LINQ query. When debugger stops in Visual Studio, then go to SQL Server Management Studio (SSMS) and start a SQL Server Profiler session.
Now press F10 in Visual Studio to step over the code statement. At this point of time you'll see absolutely no query execution in profiler session as shown below:
That is all because LINQ query didn't get executed at all till this point of time.
Now you reach to your below line of code:
foreach (var item in location)
{
var p = item.EquipmentClass.EquipmentType.Name;
}
The moment you enter inside foreach loop, the LINQ query gets fired and you'll see the corresponding log in trace session in SQL Server profiler. So a LINQ query doesn't get fired unless it is being enumerated. This is called deferred execution i.e. the runtime defers the execution until enumeration. If you never enumerate location
variable in your code then query execution will never happen at all.
So to answer your query, exception will come only when query gets fired. Not before that!
Update 1: You're saying that - I do not get a null reference exception. Yes! you will not get null reference exception until you reach to the record whose corresponding RHS joined record is missing. Have a look at below code for better understanding:
class Program
{
static void Main(string[] args)
{
var mylist1 = new List<MyClass1>();
mylist1.Add(new MyClass1 { id = 1, Name1 = "1" });
mylist1.Add(new MyClass1 { id = 2, Name1 = "2" });
var mylist2 = new List<MyClass2>();
mylist2.Add(new MyClass2 { id = 1, Name2 = "1" });
var location = from l in mylist1
join e in mylist2 on l.id equals e.id into rs1
from e in rs1.DefaultIfEmpty()
//where ids.Contains(l.ID)
select new
{
EquipmentClass = e,
InServiceStatus = e == null ? 1 : e.id,
EquipmentType = e.id
};
foreach (var item in location)
{
}
}
}
class MyClass1
{
public int id { get; set; }
public string Name1 { get; set; }
}
class MyClass2
{
public int id { get; set; }
public string Name2 { get; set; }
}
So, Now when I start iterating the location
variable, it doesn't breaks in first iteration. It breaks in second iteration. It breaks when it fails to obtain the record/object corresponding to MyClass1
object having id
2 present in mylist1
. mylist2
doesn't have any object with id
2. Hope this helps!
Upvotes: 1