Entity Framework:从 EF5 升级到 EF6 后,使用 == 的任何 (..) 过滤匹配 NULL
Entity Framework: Any(..)-filtering with == matches NULL after upgrading from EF5 to EF6
作为将我的 ASP.NET MVC 项目升级到目标 .NET Framework v4.8 和 ASP.NET MVC v5.2.7 的一部分,Entity Framework 也从 v5.0.0 升级到 v6 .4.4;在那之后我注意到使用 Any()
构造的一个奇怪的变化。
顺便说一句,我也按照这里的步骤操作:https://docs.microsoft.com/en-us/ef/ef6/what-is-new/upgrading-to-ef6
我的项目中有这段代码(升级前后代码相同)
[HttpPost]
public ActionResult Create(Course course)
{
...
if (context.Courses.Any(c => c.ExternalCourseNumber == course.ExternalCourseNumber))
{
throw new Exception(Resources.Global.ExternalCourseIdAlreadyExists);
}
...
}
这是一个验证,是创建新 Course
的一部分。 Course
对象是传递给 Create 方法的模型,字段 ExternalCourseNumber
在网络表单上留空,因此 course.ExternalCourseNumber
的值为空,当我在 [=57 中调试时=].
关键是,如果提供了 ExternalCourseNumber
,则任何现有课程都不能存在,但如果未提供,我不想抛出验证错误。
在升级之前,此验证将 不会 抛出验证错误,当 ExternalCourseNumber
保留 empty/null 时。升级后,我的代码抛出验证错误。
即在我看来:
Courses.Any(c => c.ExternalCourseNumber == course.ExternalCourseNumber)
升级后与 null 比较时的行为发生了变化....这就是让我感到困惑的地方?!?
我知道如何通过同时测试空值来解决问题,但行为的变化让我担心,因为它也可能影响其他功能,所以我想了解发生了什么?
== 运算符是否开始也匹配 NULL 值,还是我遗漏了什么?
=== 更新:SQL 从 EF6 生成 =====
我设法使用 EF6 日志记录框架来查看 SQL:
SELECT
CASE WHEN ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Courses] AS [Extent1]
WHERE ([Extent1].[ExternalCourseNumber] = @p__linq__0) OR (([Extent1].[ExternalCourseNumber] IS NULL) AND (@p__linq__0 IS NULL))
)) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
...这样就解释了为什么查询将包括 ExternalCourseNumber 为空的课程。但是 why/when 改变了吗?为什么 EF5 的行为方式不同?这似乎是一个“重大变化”?
PS 我不知道如何查看 EF5 中生成的 SQL...有什么建议吗?
这与 数据库空语义 中的重大更改有关。
在 EF6 中,context.Courses.Any(c => c.ExternalCourseNumber == course.ExternalCourseNumber)
之类的查询将转换为:
WHERE ([Extent1].[ExternalCourseNumber] = @p__linq__0)
OR (([Extent1].[ExternalCourseNumber] IS NULL) AND (@p__linq__0 IS NULL))
(其中 @p__linq__0
是等于 null
的 (n)varchar 变量)。
在 EF5 中,这类似于:
WHERE [Extent1].[ExternalCourseNumber] = @p__linq__0
第二个查询比较null
和null
,在SQL中是未定义的,查询returns什么都没有。
在 EF6 中,通过使比较语义等于 C# 中的语义(其中 null == null
returns true
来解决此问题。这需要一个额外的 OR
谓词。第一个查询 (EF6) returns 课程,其中 ExternalCourseNumber
是 null
。
令人惊讶的是,这个重大变化没有被清楚地记录下来。在 EF5 中,ObjectContext
已经有一个可以更改行为的设置 ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior
。默认为 false
;将其设置为 true
将 SQL 翻译成第一个查询的翻译。
在 EF6 中,此设置已移至首选 DbContext
作为 DbContext.Configuration.UseDatabaseNullSemantics
,因此,实际上与 UseCSharpNullComparisonBehavior
相反。重大更改是默认值也是 false
,可能是因为 C# 语义被认为是首选行为,因为这是大多数 EF 开发人员所期望的。
在你的情况下,我不会更改 UseDatabaseNullSemantics
,而只是添加一个额外的检查,如果 course.ExternalCourseNumber
不等于 null
,因为它使代码更不言自明.
作为将我的 ASP.NET MVC 项目升级到目标 .NET Framework v4.8 和 ASP.NET MVC v5.2.7 的一部分,Entity Framework 也从 v5.0.0 升级到 v6 .4.4;在那之后我注意到使用 Any()
构造的一个奇怪的变化。
顺便说一句,我也按照这里的步骤操作:https://docs.microsoft.com/en-us/ef/ef6/what-is-new/upgrading-to-ef6
我的项目中有这段代码(升级前后代码相同)
[HttpPost]
public ActionResult Create(Course course)
{
...
if (context.Courses.Any(c => c.ExternalCourseNumber == course.ExternalCourseNumber))
{
throw new Exception(Resources.Global.ExternalCourseIdAlreadyExists);
}
...
}
这是一个验证,是创建新 Course
的一部分。 Course
对象是传递给 Create 方法的模型,字段 ExternalCourseNumber
在网络表单上留空,因此 course.ExternalCourseNumber
的值为空,当我在 [=57 中调试时=].
关键是,如果提供了 ExternalCourseNumber
,则任何现有课程都不能存在,但如果未提供,我不想抛出验证错误。
在升级之前,此验证将 不会 抛出验证错误,当 ExternalCourseNumber
保留 empty/null 时。升级后,我的代码抛出验证错误。
即在我看来:
Courses.Any(c => c.ExternalCourseNumber == course.ExternalCourseNumber)
升级后与 null 比较时的行为发生了变化....这就是让我感到困惑的地方?!?
我知道如何通过同时测试空值来解决问题,但行为的变化让我担心,因为它也可能影响其他功能,所以我想了解发生了什么?
== 运算符是否开始也匹配 NULL 值,还是我遗漏了什么?
=== 更新:SQL 从 EF6 生成 =====
我设法使用 EF6 日志记录框架来查看 SQL:
SELECT
CASE WHEN ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Courses] AS [Extent1]
WHERE ([Extent1].[ExternalCourseNumber] = @p__linq__0) OR (([Extent1].[ExternalCourseNumber] IS NULL) AND (@p__linq__0 IS NULL))
)) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
...这样就解释了为什么查询将包括 ExternalCourseNumber 为空的课程。但是 why/when 改变了吗?为什么 EF5 的行为方式不同?这似乎是一个“重大变化”?
PS 我不知道如何查看 EF5 中生成的 SQL...有什么建议吗?
这与 数据库空语义 中的重大更改有关。
在 EF6 中,context.Courses.Any(c => c.ExternalCourseNumber == course.ExternalCourseNumber)
之类的查询将转换为:
WHERE ([Extent1].[ExternalCourseNumber] = @p__linq__0)
OR (([Extent1].[ExternalCourseNumber] IS NULL) AND (@p__linq__0 IS NULL))
(其中 @p__linq__0
是等于 null
的 (n)varchar 变量)。
在 EF5 中,这类似于:
WHERE [Extent1].[ExternalCourseNumber] = @p__linq__0
第二个查询比较null
和null
,在SQL中是未定义的,查询returns什么都没有。
在 EF6 中,通过使比较语义等于 C# 中的语义(其中 null == null
returns true
来解决此问题。这需要一个额外的 OR
谓词。第一个查询 (EF6) returns 课程,其中 ExternalCourseNumber
是 null
。
令人惊讶的是,这个重大变化没有被清楚地记录下来。在 EF5 中,ObjectContext
已经有一个可以更改行为的设置 ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior
。默认为 false
;将其设置为 true
将 SQL 翻译成第一个查询的翻译。
在 EF6 中,此设置已移至首选 DbContext
作为 DbContext.Configuration.UseDatabaseNullSemantics
,因此,实际上与 UseCSharpNullComparisonBehavior
相反。重大更改是默认值也是 false
,可能是因为 C# 语义被认为是首选行为,因为这是大多数 EF 开发人员所期望的。
在你的情况下,我不会更改 UseDatabaseNullSemantics
,而只是添加一个额外的检查,如果 course.ExternalCourseNumber
不等于 null
,因为它使代码更不言自明.