问题 如果使用nullable int变量LINQ返回0结果,如果使用“null”则返回准确结果


我有一个名为“test”的表,它只有1列,“NullableInt”(可为空的int类型)

记录是:1,2,null

int? nullableInt = null;
var t = db.tests.Where(x => x.NullableInt == null).ToList(); // returns 1 record
var t2 = db.tests.Where(x => x.NullableInt == nullableInt).ToList(); // returns 0 records

出于某种原因,t2返回0条记录,即使它使用的是“nullableInt”变量,其值为null,就像t,它与“null”进行比较

任何帮助将不胜感激!


1225
2018-02-10 04:30


起源



答案:


查询可以这种方式构建:

var q = db.tests;
if(nullableInt.HasValue)
{
   q = q.Where(x => x.NullableInt == nullableInt.Value);
}
else
{
   q = q.Where(x => x.NullableInt == null);
}
var t2 = q.ToList();

2
2018-02-10 05:49



+1很糟糕,但这是它看起来唯一的方式=( - Francisco
请参阅下面的答案,这在EF6中已修复,您可以选择加入EF5中的修复程序。 - Rowan Miller


答案:


查询可以这种方式构建:

var q = db.tests;
if(nullableInt.HasValue)
{
   q = q.Where(x => x.NullableInt == nullableInt.Value);
}
else
{
   q = q.Where(x => x.NullableInt == null);
}
var t2 = q.ToList();

2
2018-02-10 05:49



+1很糟糕,但这是它看起来唯一的方式=( - Francisco
请参阅下面的答案,这在EF6中已修复,您可以选择加入EF5中的修复程序。 - Rowan Miller


是的 - 这是LINQ-to-SQL / Entity Framework中的一个错误。 IS NULL 如果您将null硬编码到查询中,而不是当前为空的变量,则只会生成查询。

第二个查询将生成

SELECT .......
WHERE NullableInt == @someParam
WHERE @someParam is null.

第一个会产生适当的 IS NULL 在里面 WHERE 条款。

如果你正在使用LINQ-to-SQL,你可以将你的查询记录到Console.Out以便自己查看,如果你正在使用EF,那么ToTraceString()应该显示相同的信息(或SQL Server探查器)


6
2018-02-10 04:34



实际上我不认为这是一个错误,因为在第二个表达式中 nullableInt 不是 null 即使你赋予它的价值 nullableInt = null(因为它是一个结构,其值不能为null)。因此,框架将其视为其他结构(例如int)。 - Danny Chen
@Danny,那是分裂的头发。错误可能在规范中,即使代码完全按照规范所说的那样做。换句话说,这里的错误可能不是代码碰巧做错了什么,而是有人没有考虑这种情况。 - Lasse Vågsæther Karlsen
确实分裂了毛发。没有正当理由可以解释为什么EF解析器应该生成第二个查询而不是IS NULL。这已被记录为MS的错误(具有非常高的投票数),尽管我手边没有该链接。 - Adam Rackis


TL;博士

如果在EF6中使用DbContext,则修复此问题。

如果您正在使用EF5(或EF6中的ObjectContext),则需要将ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior设置为true。要在DbContext上执行此操作,请使用以下命令:

((IObjectContextAdapter)db).ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior = true;

更多细节

此问题的根本原因是数据库如何比较空值以及C#如何比较空值的差异。因为您在C#中编写查询,所以您希望使用C#的语义。

在EF5中,我们引入了ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior,它允许您选择使用C#语义而不是数据库语义。默认值为false(以便在升级到EF5时,现有查询不会神奇地开始返回不同的结果)。但您可以将其设置为true,并且您的查询都将返回行。

如果您在EF5中使用DbContext,则需要下拉到ObjectContext来设置它:

((IObjectContextAdapter)db).ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior = true;

如果你正在使用EF6,那么它已经在DbContext上设置为true,所以你很高兴。我们认为这导致了如此多的混乱,值得对现有查询产生潜在影响。


2
2018-06-12 18:15



谢谢罗恩,这非常有帮助。 - Eric


还有另一个解决方案永远有效,尽管有一个小小的警告:

int? nullableInt = null;
var t2 = db.tests.Where(x => object.Equals(x.NullableInt, nullableInt)).ToList();

当值为null时,您将获得正确的值 IS NULL 查询,但是当它不为null时,你会得到类似的东西:

SELECT ...
WHERE ([t0].[NullableInt] IS NOT NULL) AND ([t0].[NullableInt] = @p0) 

显然它有一个额外的条件(其来源有点令人费解)。话虽这么说,SQL Server的查询优化器应检测到,因为@ p0是一个非空值,第一个条件是超集,并将删除where子句。


0
2018-02-10 11:28



这可能适用于LINQ-to-SQL(我还没有测试过),但肯定会 不 在EF4工作。它仍然生成相同的old = @param,其中@param设置为null - Adam Rackis
啊,所以他们“修理”了它;)。是的,在L2S中它可行。 - mmix


会做:

var t2 = db.tests.Where(x => x.NullableInt == nullableInt ?? null).ToList(); 

工作?

但这似乎完全是疯狂的。


0
2018-02-10 12:21



确实是疯了,不,它不起作用。我尝试输入生成的SQl,但是它一直在失败 - 我猜它正在防止某种注入攻击 - Adam Rackis