EF Core 9 (EF9) 中的中断性变更
此页面记录了可能会导致现有应用程序从 EF Core 8 更新到 EF Core 9 时中断的 API 和行为变更。 如果从早期版本的 EF Core 进行更新,请务必查看之前的中断性变更:
目标 Framework
EF Core 9 面向 .NET 8。 这意味着面向 .NET 8 的现有应用程序可以继续面向它。 面向较旧的 .NET、.NET Core 和 .NET Framework 版本的应用程序需要面向 .NET 8 或 .NET 9 才能使用 EF Core 9。
总结
注意
如果使用 Azure Cosmos DB,请参阅以下有关 Azure Cosmos DB 重大更改的单独部分。
中断性变更 | 影响 |
---|---|
EF.Functions.Unhex() 现在返回 byte[]? |
低 |
SqlFunctionExpression 的为 Null 性参数的 arity 已验证 | 低 |
ToString() 方法现在为 null 实例返回空字符串 |
低 |
共享框架依赖项已更新为 9.0.x | 低 |
影响较小的更改
EF.Functions.Unhex()
现在返回 byte[]?
旧行为
EF.Functions.Unhex()
函数之前被批注为返回 byte[]
。
新行为
从 EF Core 9.0 开始,Unhex() 现在批注为返回 byte[]?
。
原因
Unhex()
转换为 SQLite unhex
函数,该函数对无效输入返回 NULL。 因此,对于这些情况,Unhex()
返回了 null
,这违反了注释。
缓解措施
如果你确定传递给 Unhex()
的文本内容表示有效的十六进制字符串,则只需添加一个 null 包容运算符作为调用永远不会返回 null 的断言:
var binaryData = await context.Blogs.Select(b => EF.Functions.Unhex(b.HexString)!).ToListAsync();
否则,在 Unhex() 的返回值上添加对 null 的运行时检查。
SqlFunctionExpression 的为 Null 性参数的 arity 已验证
旧行为
以前,可以创建具有不同参数数量和为 Null 性传播参数的 SqlFunctionExpression
。
新行为
从 EF Core 9.0 开始,如果参数数量和为 Null 性传播参数不匹配,EF 将引发异常。
原因
参数数量和为 Null 性传播参数不匹配可能会导致意外行为。
缓解措施
请确保 argumentsPropagateNullability
的元素数与 arguments
相同。 如有疑问,请使用 false
来获取为 Null 性参数。
ToString()
方法现在为 null
实例返回空字符串
旧行为
以前,当参数值为 null
时,EF 为 ToString()
方法返回了不一致的结果。 例如,ToString()
对于 null
值的 bool?
属性,返回 null
;但对于值为 null
的非属性表达式 bool?
,返回True
。 其他数据类型的行为也不一致,例如 null
值枚举上的 ToString()
返回空字符串。
新行为
从 EF Core 9.0 开始,当参数值为 null
时,ToString()
方法现在在所有情况下都始终返回空字符串。
原因
旧的行为在不同的数据类型和情况下是不一致的,也与 C# 行为不一致。
缓解措施
若要恢复到旧行为,请相应地重写查询:
var newBehavior = context.Entity.Select(x => x.NullableBool.ToString());
var oldBehavior = context.Entity.Select(x => x.NullableBool == null ? null : x.NullableBool.ToString());
共享框架依赖项已更新为 9.0.x
旧行为
使用 Microsoft.NET.Sdk.Web
SDK 并以 net8.0 为目标的应用程序将从共享框架中解析 System.Text.Json
、Microsoft.Extensions.Caching.Memory
、Microsoft.Extensions.Configuration.Abstractions
、Microsoft.Extensions.Logging
和 Microsoft.Extensions.DependencyModel
等包,因此这些程序集通常不会与应用一起部署。
新行为
虽然 EF Core 9.0 仍然支持 net8.0,但它现在引用了 9.0.x 版本的 System.Text.Json
、Microsoft.Extensions.Caching.Memory
、Microsoft.Extensions.Configuration.Abstractions
、Microsoft.Extensions.Logging
和 Microsoft.Extensions.DependencyModel
。 以 net8.0 为目标的应用将无法利用共享框架来避免部署这些程序集。
原因
匹配的依赖项版本包含最新的安全修补程序,并使用它们简化了 EF Core 的服务模型。
缓解措施
将应用更改为以 net9.0 为目标以获得先前的行为。
Azure Cosmos DB 重大更改
在 9.0 版本中,为了使 Azure Cosmos DB 提供程序更好,已经进行了大量的工作。 这些更改包括许多影响重大的重大更改;如果要升级现有应用程序,请仔细阅读以下内容。
影响较大的更改
鉴别器属性现在命名为 $type
,而不是 Discriminator
旧行为
EF 会自动将鉴别器属性添加到 JSON 文档中,以标识文档所代表的实体类型。 在 EF 的早期版本中,此 JSON 属性默认命名为 Discriminator
。
新行为
从 EF Core 9.0 开始,鉴别器属性现在默认称为 $type
。 如果 Azure Cosmos DB 中有来自早期版本 EF 的现有文档,这些文档将使用旧的 Discriminator
命名,升级到 EF 9.0 后,对这些文档的查询将失败。
原因
一种新兴的 JSON 做法在需要标识文档类型的方案中使用 $type
属性。 例如,.NET 的 System.Text.Json 还支持多态性,使用 $type
作为其默认的鉴别器属性名称 (docs)。 为了与生态系统的其余部分保持一致,并使其更容易与外部工具进行互操作,默认值已更改。
缓解措施
最简单的缓解措施是只需将鉴别器属性的名称配置为 Discriminator
,就像之前一样:
modelBuilder.Entity<Session>().HasDiscriminator<string>("Discriminator");
对所有顶级实体类型执行此操作将使 EF 的行为与以前一样。
此时,如果需要,还可以更新所有文档以使用新的 $type
命名。
默认情况下,id
属性现在只包含 EF 键属性
旧行为
以前,EF 将实体类型的鉴别器值插入到文档的 id
属性中。 例如,如果保存了一个 Blog
实体类型,其 Id
属性包含 8,则 JSON id
属性将包含 Blog|8
。
新行为
从 EF Core 9.0 开始,JSON id
属性不再包含鉴别器值,只包含键属性的值。 对于上面的示例,JSON id
属性是 8
。 如果 Azure Cosmos DB 中的现有文档来自以前版本的 EF,则这些文档在 JSON id
属性中具有鉴别器值,升级到 EF 9.0 后,对这些文档的查询将失败。
原因
由于 JSON id
属性必须是唯一的,因此之前向其添加了鉴别器,以允许存在具有相同键值的不同实体。 例如,这允许在同一个容器和分区中同时具有一个 Blog
和一个 Post
以及一个包含值 8 的 Id
属性。 这与关系数据库数据建模模式更为一致,其中每个实体类型都映射到自己的表,因此有自己的键空间。
EF 9.0 通常会更改映射,使其更符合常见的 Azure Cosmos DB NoSQL 做法和期望,而不是符合来自关系数据库的用户的期望。 此外,在 id
属性中设置鉴别器值会使外部工具和系统更难与 EF 生成的 JSON 文档交互;这样的外部系统通常不知道 EF 鉴别器值,默认情况下,EF 鉴别器值来自 .NET 类型。
缓解措施
最简单的缓解方法是简单地配置 EF,将鉴别器包含在 JSON id
属性中,如前所述。 为此,引入了一个新的配置选项:
modelBuilder.Entity<Session>().HasDiscriminatorInJsonId();
对所有顶级实体类型执行此操作将使 EF 的行为与以前一样。
此时,如果需要,还可以更新所有文档以重写其 JSON id
属性。 请注意,只有当不同类型的实体在同一容器中不共享相同的 id 值时,才能执行此操作。
影响中等的更改
不再支持通过 Azure Cosmos DB 提供程序来同步 I/O
旧行为
以前,调用同步方法(如 ToList
或 SaveChanges
)会导致 EF Core 在对 Azure Cosmos DB SDK 执行异步调用时阻止同步使用 .GetAwaiter().GetResult()
。 这可能会导致死锁。
新行为
从 EF Core 9.0 开始,EF 现在会在尝试使用同步 I/O 时默认引发。 异常消息为“Azure Cosmos DB 不支持同步 I/O。 在使用 Entity Framework Core 访问 Azure Cosmos DB 时,请确保仅使用和正确等待异步方法。 有关详细信息,请参阅 https://aka.ms/ef-cosmos-nosync。”
原因
异步方法上的同步阻塞可能会导致死锁,而 Azure Cosmos DB SDK 又仅支持异步方法。
缓解措施
在 EF Core 9.0 中,可以通过以下方法防止该错误:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.ConfigureWarnings(w => w.Ignore(CosmosEventId.SyncNotSupported));
}
也就是说,应用程序应停止将同步 API 与 Azure Cosmos DB 配合使用,因为 Azure Cosmos DB SDK 不支持此功能。 在 EF Core 的未来版本中,将移除可防止该异常的功能,之后使用异步 API 便成为了唯一的选项。
SQL 查询现在必须直接投影 JSON 值
旧行为
以前,EF 生成的查询如下:
SELECT c["City"] FROM root c
此类查询会导致 Azure Cosmos DB 将每个结果包装在 JSON 对象中,如下所示:
[
{
"City": "Berlin"
},
{
"City": "México D.F."
}
]
新行为
从 EF Core 9.0 开始,EF 现在将 VALUE
修饰符添加到查询,如下所示:
SELECT VALUE c["City"] FROM root c
此类查询会导致 Azure Cosmos DB 直接返回值,而不进行包装:
[
"Berlin",
"México D.F."
]
如果应用程序使用 SQL 查询,那么在升级到 EF 9.0 后,这些查询可能会被破坏,因为它们不包括 VALUE
修饰符。
原因
在一些情况下,将每个结果包装在一个额外的 JSON 对象中可能会导致性能下降,导致 JSON 结果有效负载膨胀,并且不是使用 Azure Cosmos DB 的自然方式。
缓解措施
若要缓解问题,只需将 VALUE
修饰符添加到 SQL 查询的投影中,如下所示。
未定义的结果现在会自动从查询结果中筛选出来
旧行为
以前,EF 生成的查询如下:
SELECT c["City"] FROM root c
此类查询会导致 Azure Cosmos DB 将每个结果包装在 JSON 对象中,如下所示:
[
{
"City": "Berlin"
},
{
"City": "México D.F."
}
]
如果任何结果未定义(例如,文档中缺少 City
属性),则返回一个空文档,EF 将为该结果返回 null
。
新行为
从 EF Core 9.0 开始,EF 现在将 VALUE
修饰符添加到查询,如下所示:
SELECT VALUE c["City"] FROM root c
此类查询会导致 Azure Cosmos DB 直接返回值,而不进行包装:
[
"Berlin",
"México D.F."
]
Azure Cosmos DB 的行为是自动从结果中筛选出 undefined
值;这意味着,如果文档中缺少 City
属性之一,查询将只返回一个结果,而不是两个结果,其中一个是 null
。
原因
在一些情况下,将每个结果包装在一个额外的 JSON 对象中可能会导致性能下降,导致 JSON 结果有效负载膨胀,并且不是使用 Azure Cosmos DB 的自然方式。
缓解措施
如果为未定义的结果获取 null
值对应用程序很重要,请使用新的 EF.Functions.Coalesce
运算符将 undefined
值合并到 null
:
var users = await context.Customer
.Select(c => EF.Functions.CoalesceUndefined(c.City, null))
.ToListAsync();
错误转换的查询不再转换
旧行为
以前,EF 转换的查询如下:
var sessions = await context.Sessions
.Take(5)
.Where(s => s.Name.StartsWith("f"))
.ToListAsync();
但是,此查询的 SQL 转换不正确:
SELECT c
FROM root c
WHERE ((c["Discriminator"] = "Session") AND STARTSWITH(c["Name"], "f"))
OFFSET 0 LIMIT @__p_0
在 SQL 中,WHERE
子句在 OFFSET
和 LIMIT
子句之前求值;但在上面的 LINQ 查询中,Take
运算符出现在 Where
运算符之前。 因此,此类查询可能会返回不正确的结果。
新行为
从 EF Core 9.0 开始,此类查询不再转换,并引发异常。
原因
不正确的转换可能会导致无提示数据损坏,这可能会在应用程序中引入难以发现的错误。 EF 总是倾向于通过提前引发来快速失败,而不是可能导致数据损坏。
缓解措施
如果对以前的行为感到满意,并且想要执行相同的 SQL,只需围绕 LINQ 运算符的顺序进行交换:
var sessions = await context.Sessions
.Where(s => s.Name.StartsWith("f"))
.Take(5)
.ToListAsync();
遗憾的是,Azure Cosmos DB 目前不支持 SQL 子查询中的 OFFSET
和 LIMIT
子句,而这正是原始 LINQ 查询的正确转换所需要的。
影响较小的更改
HasIndex
现在引发而不是被忽略
旧行为
以前,EF Cosmos DB 提供程序忽略了对HasIndex 的调用。
新行为
现在,如果指定了 HasIndex,则会引发提供程序。
原因
在 Azure Cosmos DB, 中,默认情况下对所有属性编制索引,无需指定索引。 虽然可以定义自定义索引策略,但 EF 目前不支持此策略,可以在没有 EF 支持的情况下通过 Azure 门户完成。 由于 HasIndex 调用没有任何作用,因此不再允许它们。
缓解措施
删除对 HasIndex 的任何调用。
在 9.0.0-rc.2 之后,IncludeRootDiscriminatorInJsonId
已重命名为 HasRootDiscriminatorInJsonId
旧行为
IncludeRootDiscriminatorInJsonId
API 是在 9.0.0 rc.1 中引入的。
新行为
对于 EF Core 9.0 的最终版本,API 已重命名为 HasRootDiscriminatorInJsonId
原因
另一个相关的 API 被重命名为以 Has
而不是 Include
开始,因此为了一致性,这个 API 也被重命名。
缓解措施
如果代码使用的是 IncludeRootDiscriminatorInJsonId
API,只需将其更改为引用 HasRootDiscriminatorInJsonId
即可。