I’m using EF 4 to PostgreSQL using a 3rd party ADO.NET provider. In my data source, I have no item lines that match items based on this join condition. For this reason I expect query 1 to fail based on a null reference exception. However, it completes and gives me value type defaults for each of the properties on il. item_display_name is a string so ilName is set to null. ilStartDate is set to DateTime’s default. x.il.item_id and x.il.item_line_no are both 0.
var query1 =
_db.items
.GroupJoin(_db.item_line.Where(x => x.start_date == selectedStartDate), x => x.item_id, il => il.item_id, (x, ilgroup) => new { x, ilgroup })
.SelectMany(x => x.ilgroup.DefaultIfEmpty(), (x, il) => new { x.x, il })
.Select(x =>
new
{
itemId = x.x.item_id,
ilName = x.il.item_display_name,
ilStartDate = x.il.start_date,
ilItemId = x.il.item_id,
orderLine = x.il.item_line_no});
However, if I force execution by calling ToArray on my results before the Select, I then get my null reference exception.
var query2 =
_db.items
.GroupJoin(_db.item_line.Where(x => x.start_date == selectedStartDate), x => x.item_id, il => il.item_id, (x, ilgroup) => new {x, ilgroup})
.SelectMany(x => x.ilgroup.DefaultIfEmpty(), (x, il) => new {x.x, il}).ToArray()
.Select(x =>
new
{
itemId = x.x.item_id,
ilName = x.il.item_display_name,
ilStartDate = x.il.start_date,
ilItemId = x.il.item_id,
orderLine = x.il.item_line_no});
To my understanding, DefaultIfEmpty should return the default for that type. My type is clearly a reference type, so why won’t query 1 fail?
This is because the first query is completely translated into SQL. SQL is different than C# when it comes to null “objects”. In SQL it is perfectly OK to write something like
It won’t crash when there are
Orders withoutOrderLines. No null reference exception onolhere. SQL just outputs null values forol.Numberwhere order lines are missing.So in the first statement the anonymous type is built directly from values obtained from SQL. The whole expression
x.il.item_display_nameis populated by output from aDbDataReader, which is null when noilgroupis present.In the second statement, first an array of objects is built in memory, consisting of
xandilpairs, some which have noil(ilis null). Now the anonymous type is built from the object array and the expressionx.il.item_display_nametries to readitem_display_namefrom some non-existing objects.