Why does the order of LET statements matter in this Entity Framework query?

时间:2015-05-12 22:22:04

标签: c# .net linq entity-framework

A query for a grid in an Entity Framework-backed .NET web application I'm working on was giving a 500 error (INCLUDE Irvine32.inc .data name BYTE "Joe Smith" , 0 ; temp BYTE SIZEOF name DUP(0) .code main PROC mov al,0 mov esi,0 mov ecx,SIZEOF name Combine: xor al,name[esi] inc esi loop Combine Call DumpRegs exit ; exit to operating system main ENDP END main ) when the grid row object happened to have zero child items in a particular one-to-many relationship. The null was coming back on an unrelated integer property. Bafflingly, reversing the order of the two independent Let statements in the Linq expression made the error go away.

That is, if there is only one Widget (ID: 1, CreatedOn: some datetime), which has no Bars and one Foo (fValue: 96)

The cast to value type 'System.Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.

or

from w in Widgets.OrderBy(w => w.CreatedOn)
let foo = w.Foos.FirstOrDefault()
let bar = w.Bars.FirstOrDefault()
select new { w.WidgetID, foo.fValue }

gives from w in Widgets let bar = w.Bars.FirstOrDefault() let foo = w.Foos.FirstOrDefault() orderby w.CreatedOn select new { w.WidgetID, foo.fValue } as expected, but

{WidgetID: 1, fValue: 96}

comes back with from w in Widgets.OrderBy(w => w.CreatedOn) let bar = w.Bars.FirstOrDefault() let foo = w.Foos.FirstOrDefault() select new { w.WidgetID, foo.fValue } which of course crashes because Foo.fValue is an integer.

All three expressions generate slightly different SQL queries under Entity Framework, which I would expect - the failing expression contains the clause

{WidgetID: 1, fValue: NULL}

which I believe is the culprit (0 Bars crossed with 1 Foo = 0 results). So I understand the "how" of the error; what gets me is that I have no idea why the order of the LETs or whether I OrderBy with a Linq method call vs a Linq expression should make a difference.

Here's the reduced table schema / data if you want to experiment yourself:

... 
(SELECT TOP (1) 
    [Extent7].[fValue] AS [fValue]
    FROM   (SELECT TOP (1) [Extent6].[BarID] AS [BarID]
        FROM [dbo].[Bars] AS [Extent6]
        WHERE [Extent1].[WidgetID] = [Extent6].[bWidgetID] ) AS [Limit5]
    CROSS JOIN [dbo].[Foos] AS [Extent7]
    WHERE [Extent1].[WidgetID] = [Extent7].[fWidgetID]) AS [C1]
...

Can you explain why those 3 expressions aren't logically equivalent in Entity Framework?

1 个答案:

答案 0 :(得分:1)

我认为这是与此知道的实体框架问题相关的错误:https://entityframework.codeplex.com/workitem/1196。根据该问题,在使用order byletFirstOrDefault时,查询树将编译为错误的SQL查询。

可悲的是,这个问题差不多有两年了,所以这个特殊的错误可能不是EF团队的高优先级。也许它将在EF7中修复!