EF Core 6.0 中的中断性变更

API 和行为的下列更改有可能导致现有应用程序在更新到 EF Core 6.0 时中断。

目标 Framework

EF Core 6.0 面向 .NET 6。 面向旧版 .NET、.NET Core 和 .NET Framework 版本的应用程序需要面向 .NET 6 才能使用 EF Core 6.0。

总结

中断性变更 影响
无法保存共享表且没有必需属性的嵌套可选依赖项
更改所拥有实体的所有者现在会引发异常
Azure Cosmos DB:相关实体类型被视为拥有
SQLite:连接已池化
无映射联接实体的多对多关系现已搭建基架
已清理 DeleteBehavior 和 ON DELETE 值之间的映射
内存中数据库验证所需属性不包含 null
删除了联接集合时的最后一个 ORDER BY
DbSet 不再实现 IAsyncEnumerable
默认情况下,TVF 返回实体类型也映射到表
现在会验证检查约束名称的唯一性
添加了 IReadOnly 元数据接口并删除了扩展方法
IExecutionStrategy 现在为单一实例服务
SQL Server:更多错误被视为暂时性错误
Azure Cosmos DB:更多字符在“id”值中进行转义
某些 Singleton 服务现在已划分范围 低*
用于添加或替换服务的扩展的新缓存 API 低*
新的快照和设计时模型初始化过程
OwnedNavigationBuilder.HasIndex 现在返回一个不同的类型
DbFunctionBuilder.HasSchema(null) 替代 [DbFunction(Schema = "schema")]
预先初始化的导航由数据库查询中的值替代
查询时,数据库中的未知枚举字符串值不会转换为枚举默认值
DbFunctionBuilder.HasTranslation 现在以 IReadOnlyList(而不是 IReadOnlyCollection)的形式提供函数参数
当实体映射到表值函数时,不会删除默认表映射
dotnet-ef 面向 .NET 6
可能需要更新IModelCacheKeyFactory实现以处理设计时缓存
默认情况下,NavigationBaseIncludeIgnored 现在为错误

* 数据库提供程序和扩展的作者对这些更改特别感兴趣。

影响较大的更改

不允许共享一个表且没有必需属性的嵌套可选依赖项

跟踪问题 #24558

旧行为

允许具有共享表的可选依赖项且没有所需属性模型,但在查询数据后再次保存时可能会导致数据丢失。 例如,请考虑以下模型:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ContactInfo ContactInfo { get; set; }
}

[Owned]
public class ContactInfo
{
    public string Phone { get; set; }
    public Address Address { get; set; }
}

[Owned]
public class Address
{
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

ContactInfoAddress 中的属性都不需要,并且所有这些实体类型都映射到同一个表。 可选依赖项的规则(与必需的依赖关系相对)指出,如果 ContactInfo 的所有列都为 NULL,则查询所有者 Customer 时不会创建 ContactInfo 的实例。 然而,这也意味着不会创建 Address 的实例,即使 Address 列是非 NULL。

新行为

尝试使用此模型将会引发以下异常:

System.InvalidOperationException:Entity type 'ContactInfo' 是一个使用表共享并包含其他依赖项的可选依赖项,它没有任何必需的非共享属性来标识实体是否存在。 如果所有可为 null 属性在数据库中都包含 null 值,则不会在查询中创建对象实例,从而导致嵌套依赖项的值丢失。 请添加一个必需属性,以使用其他属性的 null 值创建实例,或者将传入的导航属性标记为必需,以始终创建实例。

这可以防止在查询和保存数据时丢失数据。

原因

使用具有共享表的嵌套且没有必需属性的可选依赖项的模型通常会导致无提示数据丢失。

缓解措施

避免使用贡献表且没有必需属性的可选依赖项。 有三种简单方法可以实现此操作:

  1. 使依赖项成为必需项。 这意味着,在查询依赖实体后,即使其所有属性都为 null,该实体将始终具有值。 例如:

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        [Required]
        public Address Address { get; set; }
    }
    

    或:

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.OwnsOne(e => e.Address);
                b.Navigation(e => e.Address).IsRequired();
            });
    
  2. 请确保依赖项至少包含一个必需属性。

  3. 将可选的依赖项映射到其自己的表中,而不是与主体共享表。 例如:

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.ToTable("Customers");
                b.OwnsOne(e => e.Address, b => b.ToTable("CustomerAddresses"));
            });
    

有关可选依赖项的问题和这些缓解措施的示例,请参阅 EF Core 6.0 中的新增功能相应文档。

影响中等的更改

更改所拥有实体的所有者现在会引发异常

跟踪问题 #4073

旧行为

可以将拥有的实体重新分配给不同的所有者实体。

新行为

此操作现在将引发异常:

属性“{entityType}.{property}”是键的一部分,因此不能对其进行修改或将其标记为已修改。 若要使用标识外键更改现有实体的主体,请首先删除依赖项并调用 SaveChanges,然后将依赖项与新主体相关联。

原因

尽管我们不需要在拥有的类型上存在键属性,但 EF 仍会创建影子属性以用作主键,并将外键指向所有者。 如果更改了所有者实体,则会导致拥有的实体上的外键值发生更改,并且由于这些值也用作主键,因此还会导致实体标识发生更改。 此行为在 EF Core 中并不完全受支持,并且对于拥有的实体仅在特定条件下才受支持,有时会导致内部状态变得不一致。

缓解措施

你可以分配副本并删除旧实例,而不是将相同的实例分配给新的所有者。

跟踪问题 #24803新增功能:默认为隐式所有权

旧行为

与在其他提供程序中一样,相关实体类型被视为普通(非拥有)类型。

新行为

相关实体类型现在将归发现它们的实体类型所有。 仅将与属性 DbSet<TEntity> 相对应的实体类型视为非拥有。

原因

此行为遵循在 Azure Cosmos DB中建模数据的常见模式,即将相关数据嵌入到单个文档中。 Azure Cosmos DB 不能以本机方式支持联接不同的文档,因此将相关实体建模为非所有的实用性很有限。

缓解措施

若要将实体类型配置为非所有,请调用 modelBuilder.Entity<MyEntity>();

SQLite:连接已池化

跟踪问题 #13837新增功能:默认为隐式所有权

旧行为

以前,Microsoft.Data.Sqlite 中的连接未池化。

新行为

从 6.0 开始,连接现在默认进行池化。 这样,即使在 ADO.NET 连接对象关闭之后,数据库文件仍会保持打开状态。

原因

将基础连接池化极大地提高了打开和关闭 ADO.NET 连接对象的性能。 这一点在以下场景中尤其明显:在加密的情况下,打开基础连接的代价很高,或者在数据库有很多短期连接的情况下。

缓解措施

可以通过将 Pooling=False 添加到连接字符串来禁用连接池。

一些场景(如删除数据库文件)现在可能会遇到错误,指出文件仍在使用中。 可以使用 SqliteConnection.ClearPool() 在执行文件操作前手动清除连接池。

SqliteConnection.ClearPool(connection);
File.Delete(databaseFile);

无映射联接实体的多对多关系现已搭建基架

跟踪问题 #22475

旧行为

对于多对多关系,DbContext 和现有数据库中的实体类型的基架(反向工程)总是会显式将联接表映射到联接实体类型。

新行为

仅包含针对其他表的两个外键属性的简单联接表不再映射到显式实体类型,而是映射为两个联接表之间的多对多关系。

原因

不带显式联接类型的多对多关系在 EF Core 5.0 中引入,是表示简单联接表的更清晰、更自然的方式。

缓解措施

有两种缓解措施。 首选的方法是更新代码,以直接使用多对多关系。 如果联接实体类型仅包含多对多关系的两个外键,则很少有需要直接使用该类型。

或者,可以将显式联接实体添加回 EF 模型。 例如,假设 PostTag 之间有多对多的关系,请使用分部类添加回联接类型和导航:

public partial class PostTag
{
    public int PostsId { get; set; }
    public int TagsId { get; set; }

    public virtual Post Posts { get; set; }
    public virtual Tag Tags { get; set; }
}

public partial class Post
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

public partial class Tag
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

然后为联接类型添加配置,并为 DbContext 的分部类添加导航:

public partial class DailyContext
{
    partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>(entity =>
        {
            entity.HasMany(d => d.Tags)
                .WithMany(p => p.Posts)
                .UsingEntity<PostTag>(
                    l => l.HasOne<Tag>(e => e.Tags).WithMany(e => e.PostTags).HasForeignKey(e => e.TagsId),
                    r => r.HasOne<Post>(e => e.Posts).WithMany(e => e.PostTags).HasForeignKey(e => e.PostsId),
                    j =>
                    {
                        j.HasKey("PostsId", "TagsId");
                        j.ToTable("PostTag");
                    });
        });
    }
}

最后,从构建基架的上下文中删除多对多关系生成的配置。 这是必需的,因为必须先从模型中删除构建的联接实体类型,然后才能使用显式类型。 每次构建上下文基架时,都需要删除此代码,但由于上面的代码位于分部类中,因此它将保持不变。

请注意,使用此配置,可以显式使用联接实体,就像在 EF Core 之前的版本中一样。 但是,这种关系也可以用作多对多关系。 这意味着更新此类代码可能是一个临时解决方案,而代码的其余部分将更新为以自然方式将该关系用作多对多的关系。

影响较小的更改

已清理 DeleteBehavior 和 ON DELETE 值之间的映射

跟踪问题 #21252

旧行为

在迁移和基架搭建过程中,关系的 OnDelete() 行为与数据库中外键的 ON DELETE 行为之间的某些映射不一致。

新行为

下表说明了迁移后的变化。

OnDelete() ON DELETE
NoAction NO ACTION
ClientNoAction NO ACTION
限制 RESTRICT
Cascade CASCADE
ClientCascade RESTRICT NO ACTION
SetNull SET NULL
ClientSetNull RESTRICT NO ACTION

构建基架后的变化如下。

ON DELETE OnDelete()
NO ACTION ClientSetNull
RESTRICT ClientSetNull Restrict
CASCADE Cascade
SET NULL SetNull

原因

新映射更加一致。 现在,NO ACTION 的默认数据库行为优先于限制性更强、性能较低的 RESTRICT 行为。

缓解措施

可选关系的默认 OnDelete() 行为是 ClientSetNull。 其映射已从 RESTRICT 更改为 NO ACTION。 这可能会导致在升级到 EF Core 6.0 之后添加的第一次迁移中生成许多操作。

你可以选择应用这些操作,也可以从迁移中手动删除它们,因为它们对 EF Core 没有功能性影响。

SQL Server 不支持 RESTRICT,因此这些外键已使用 NO ACTION 创建。 迁移操作对 SQL Server 不会有任何影响,可以安全地删除它们。

内存中数据库验证所需属性不包含 NULL

跟踪问题 #10613

旧行为

即使根据需要配置了属性,内存数据库也允许保存 NULL 值。

新行为

如果调用了 SaveChangesSaveChangesAsync,并将所需的属性设置为 NULL,则内存中数据库会引发 Microsoft.EntityFrameworkCore.DbUpdateException

原因

现在,内存中的数据库行为与其他数据库的行为匹配。

缓解措施

以前的行为(即不检查 NULL 值)可以在配置内存中提供程序时还原。 例如:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseInMemoryDatabase("MyDatabase", b => b.EnableNullChecks(false));
}

删除了联接集合时的最后一个 ORDER BY

跟踪问题 #19828

旧行为

对集合(一对多关系)执行 SQL 联接时,EF Core 过去常常会为联接的表的每个键列添加一个 ORDER BY。 例如,通过以下 SQL 加载所有博客及其相关的帖子:

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId], [p].[PostId]

这些排序对于正确的实体具体化是必需的。

新行为

现在省略了集合联接的最后一个 ORDER BY:

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]

不再为帖子的 ID 列生成 ORDER BY。

原因

每个 ORDER BY 都会在数据库端强加额外的工作,并且最后的排序对于 EF Core 的具体化而言不是必需的。 数据表明,在某些情况下,删除最后的排序可能会显著提高性能。

缓解措施

如果你的应用程序需要按特定顺序返回联接的实体,请通过将 LINQ OrderBy 运算符添加到查询来明确这一点。

DbSet 不再实现 IAsyncEnumerable

跟踪问题 #24041

旧行为

DbSet<TEntity>,用于在 DbContext 上执行查询以实现 IAsyncEnumerable<T>

新行为

DbSet<TEntity> 不再直接实现 IAsyncEnumerable<T>

原因

DbSet<TEntity> 最初用于实现 IAsyncEnumerable<T>,其主要目的是通过 foreach 构造直接进行枚举。 遗憾的是,当项目还引用了 System.Linq.Async 以便编写异步 Linq 运算符客户端时,这会导致在 IQueryable<T> 定义的运算符与 IAsyncEnumerable<T> 定义的运算符之间出现模棱两可的调用错误。 C# 9 增加了对foreach循环扩展GetEnumerator支持,并删除了引用 IAsyncEnumerable 的原始主要原因。

绝大部分 DbSet 用法仍然和原来一样,要么通过 DbSet 编写 LINQ 运算符,要么直接枚举它,等等。唯一有变化的用法是尝试直接将 DbSet 强制转换为 IAsyncEnumerable

缓解措施

如果需要将 IAsyncEnumerable<T> 引用为 DbSet<TEntity>,请调用 DbSet<TEntity>.AsAsyncEnumerable 以显式转换它。

默认情况下,TVF 返回实体类型也映射到表

跟踪问题 #23408

旧行为

当实体类型用作配置有 HasDbFunction 的 TVF 的返回类型时,默认情况下它们不会映射到表。

新行为

用作 TVF 返回类型的实体类型保留默认表映射。

原因

配置 TVF 会删除返回实体类型的默认表映射,但这并不直观。

缓解措施

若要删除默认表映射,请调用 ToTable(EntityTypeBuilder, String)

modelBuilder.Entity<MyEntity>().ToTable((string?)null));

现在会验证检查约束名称的唯一性

跟踪问题 #25061

旧行为

允许在同一个表上声明和使用同名的检查约束。

新行为

在同一个表上显式配置两个同名的检查约束现在将导致出现异常。 由约定创建的检查约束将分配一个唯一名称。

原因

大多数数据库都不支持在同一个表中创建两个同名的检查约束,有些数据库甚至要求检查约束在整个表中必须唯一。 这可能会导致在应用迁移时引发异常。

缓解措施

在某些情况下,由于此更改,有效的检查约束名称可能有所不同。 若要显式指定所需名称,请调用 HasName

modelBuilder.Entity<MyEntity>().HasCheckConstraint("CK_Id", "Id > 0", c => c.HasName("CK_MyEntity_Id"));

添加了 IReadOnly 元数据接口并删除了扩展方法

跟踪问题 #19213

旧行为

有三组元数据接口:IModelIMutableModelIConventionModel 以及扩展方法。

新行为

添加了一组新的 IReadOnly 接口,例如 IReadOnlyModel。 以前为元数据接口定义的扩展方法已转换为默认接口方法。

原因

默认接口方法支持重写实现,新运行时模型实现利用这种行为来提供更好的性能。

缓解措施

这些更改不应影响大多数代码。 但是,如果通过静态调用语法使用扩展方法,则需要将其转换为实例调用语法。

IExecutionStrategy 现在为单一实例服务

跟踪问题 #21350

新行为

IExecutionStrategy 现在为单一实例服务。 这意味着自定义实现中的任何“已添加”状态在第二次执行操作时将保持不变,并且传递给 ExecutionStrategy 的委托将仅执行一次。

原因

这减少了 EF 中两个热路径的分配。

缓解措施

ExecutionStrategy 派生的实现应清除 OnFirstExecution() 中的任何状态。

传递给 ExecutionStrategy 的委托中的条件逻辑应移至 IExecutionStrategy 的自定义实现。

SQL Server:更多错误被视为暂时性错误

跟踪问题 #25050

新行为

上面问题中列出的错误现在被视为暂时性错误。 使用默认(非重试)执行策略时,这些错误现在将包装在一个添加异常实例中。

原因

我们将继续从用户和 SQL Server 团队那里收集关于哪些错误应被视为暂时性错误方面的反馈。

缓解措施

若要更改被视为暂时性的错误集,请使用可能派生自 SqlServerRetryingExecutionStrategy - 连接复原能力 - EF Core 的自定义执行策略。

Azure Cosmos DB:更多字符在“id”值中进行转义

跟踪问题 #25100

旧行为

在 EF Core 5 中,只有 '|' 值会在 id 值中进行转义。

新行为

在 EF Core 6 中,'/''\''?''#' 都会在 id 值中进行转义。

原因

Resource.Id 中所述,这些字符都无效。在 id 中使用它们将导致查询失败。

缓解措施

可替代生成的值,方法是在实体被标记为 Added 之前对其进行设置:

var entry = context.Attach(entity);
entry.Property("__id").CurrentValue = "MyEntity|/\\?#";
entry.State = EntityState.Added;

某些 Singleton 服务现在已划分范围

跟踪问题 #25084

新行为

许多查询服务和注册为 Singleton 的一些设计时服务现在注册为 Scoped

原因

必须更改生存期才能使新功能 (DefaultTypeMapping) 影响查询。

设计时服务生存期已调整为与运行时服务生存期一致,以避免同时使用这两者时出现错误。

缓解措施

使用 TryAdd 通过默认生存期注册 EF Core 服务。 对于不是由 EF 添加的服务,请仅使用 TryAddProviderSpecificServices

用于添加或替换服务的扩展的新缓存 API

跟踪问题 #19152

旧行为

在 EF Core 5 中,GetServiceProviderHashCode 返回 long 并直接用作服务提供程序的缓存键的一部分。

新行为

GetServiceProviderHashCode 现在会返回 int,并且仅用于计算服务提供程序的缓存键的哈希代码。

此外,还需要实现 ShouldUseSameServiceProvider 以指示当前对象是否表示相同的服务配置,以便可以使用同一个服务提供程序。

原因

仅将哈希代码用作缓存键的一部分会导致偶尔出现难以诊断和修复的冲突。 附加方法可确保仅在合适时使用同一个服务提供程序。

缓解措施

许多扩展不会公开任何影响已注册服务的选项,并且可以使用 ShouldUseSameServiceProvider 的以下实现:

private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
    public ExtensionInfo(IDbContextOptionsExtension extension)
        : base(extension)
    {
    }

    ...

    public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
        => other is ExtensionInfo;
}

否则,应添加其他谓词来比较所有相关选项。

新的快照和设计时模型初始化过程

跟踪问题 #22031

旧行为

在 EF Core 5 中,需要先调用特定约定,然后才能使用快照模型。

新行为

引入了 IModelRuntimeInitializer 以隐藏某些必需的步骤,并引入了一个运行时模型,该模型不具有所有迁移元数据,因此对于模型差异应使用设计时模型。

原因

IModelRuntimeInitializer 抽象出模型完成步骤,因此现在可以更改这些步骤,而无需为用户进一步进行中断性变更。

引入了经过优化的运行时模型以提高运行时性能。 实现了一些优化,其中一种是删除了运行时未使用的元数据。

缓解措施

以下代码片段演示如何检查当前模型是否不同于快照模型:

var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;

if (snapshotModel is IMutableModel mutableModel)
{
    snapshotModel = mutableModel.FinalizeModel();
}

if (snapshotModel != null)
{
    snapshotModel = context.GetService<IModelRuntimeInitializer>().Initialize(snapshotModel);
}

var hasDifferences = context.GetService<IMigrationsModelDiffer>().HasDifferences(
    snapshotModel?.GetRelationalModel(),
    context.GetService<IDesignTimeModel>().Model.GetRelationalModel());

此代码片段显示如何通过在外部创建模型并调用 UseModel 来实现 IDesignTimeDbContextFactory<TContext>

internal class MyDesignContext : IDesignTimeDbContextFactory<MyContext>
{
    public TestContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder();
        optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DB"));

        var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder();
        CustomizeModel(modelBuilder);
        var model = modelBuilder.Model.FinalizeModel();

        var serviceContext = new MyContext(optionsBuilder.Options);
        model = serviceContext.GetService<IModelRuntimeInitializer>().Initialize(model);
        return new MyContext(optionsBuilder.Options);
    }
}

OwnedNavigationBuilder.HasIndex 现在返回一个不同的类型

跟踪问题 #24005

旧行为

在 EF Core 5 中,HasIndex 返回 IndexBuilder<TEntity>,其中 TEntity 是所有者类型。

新行为

HasIndex 现在返回 IndexBuilder<TDependentEntity>,其中 TDependentEntity 是拥有的类型。

原因

返回的生成器对象未正确键入。

缓解措施

根据最新版本的 EF Core 重新编译程序集可以修复此更改导致的任何问题。

DbFunctionBuilder.HasSchema(null) 替代 [DbFunction(Schema = "schema")]

跟踪问题 #24228

旧行为

在 EF Core 5 中,通过 null 值调用 HasSchema 不会存储配置源,因此 DbFunctionAttribute 可以替代它。

新行为

现在,通过 null 值调用 HasSchema 将存储配置源,并阻止特性将其替代。

原因

使用 ModelBuilder API 指定的配置不得被数据注释替代。

缓解措施

删除 HasSchema 调用,使特性配置架构。

预先初始化的导航由数据库查询中的值替代

跟踪问题 #23851

旧行为

对于跟踪查询,设置为空对象的导航属性仍保持不变,但对于非跟踪,查询将被覆盖。 例如,请考虑以下实体类型:

public class Foo
{
    public int Id { get; set; }

    public Bar Bar { get; set; } = new(); // Don't do this.
}

public class Bar
{
    public int Id { get; set; }
}

Foo(包括Bar)的非跟踪查询将 Foo.Bar 设置为从数据库查询的实体。 例如,此代码:

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

已打印 Foo.Bar.Id = 1

但是,用于跟踪的相同查询没有使用从数据库查询的实体替代 Foo.Bar。 例如,此代码:

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

已打印 Foo.Bar.Id = 0

新行为

在 EF Core 6.0 中,跟踪查询的行为现在与非跟踪查询的行为匹配。 这意味着这两个代码:

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

此代码:

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

打印 Foo.Bar.Id = 1

原因

进行此更改原因的有两个:

  1. 确保跟踪和非跟踪查询的行为一致。
  2. 查询数据库时,假定应用程序代码想要获取数据库中存储的值很合理。

缓解措施

有两种缓解措施:

  1. 请不要从数据库中查询结果中不应包括的对象。 例如,在上面的代码段中,如果 Bar 实例不应从数据库返回并包含在结果中,则不要使用 IncludeFoo.Bar
  2. 在从数据库中查询后,设置导航的值。 例如,在上述代码片段中,运行查询后调用 foo.Bar = new()

另外,请考虑不要将相关实体实例初始化为默认对象。 这意味着相关实例是新的实体,没有保存到数据库中,没有设置键值。 如果数据库中存在相关实体,则代码中的数据与数据库中存储的数据根本不相同。

查询时,数据库中的未知枚举字符串值不会转换为枚举默认值

跟踪问题 #24084

旧行为

可以使用 HasConversion<string>()EnumToStringConverter 将枚举属性映射到数据库中的字符串列。 这会导致 EF Core 将列中的字符串值转换为匹配的 .NET 枚举类型的成员。 但是,如果字符串值与枚举成员不匹配,则该属性会设置为枚举的默认值。

新行为

EF Core 6.0 现在引发 InvalidOperationException,并出现消息“无法将字符串值‘{value}’从数据库转换为映射的‘{enumType}’枚举中的任何值。”

原因

如果将实体稍后保存回数据库,则转换为默认值可能会导致数据库损坏。

缓解措施

理想情况下,请确保数据库列仅包含有效的值。 或者,使用旧行为实现 ValueConverter

DbFunctionBuilder.HasTranslation 现在以 IReadOnlyList(而不是 IReadOnlyCollection)的形式提供函数参数

跟踪问题 #23565

旧行为

使用 HasTranslation 方法配置用户定义函数的转换时,函数的参数以 IReadOnlyCollection<SqlExpression> 的形式提供。

新行为

在 EF Core 6.0 中,参数现在以 IReadOnlyList<SqlExpression> 的形式提供。

原因

IReadOnlyList 允许使用索引器,因此,这些参数现在更易于访问。

缓解措施

无。 IReadOnlyList 实现 IReadOnlyCollection 接口,因此转换应该非常简单。

当实体映射到表值函数时,不会删除默认表映射

跟踪问题 #23408

旧行为

将实体映射到表值函数时,将删除其对表的默认映射。

新行为

在 EF Core 6.0 中,实体仍然使用默认映射映射到一个表,即使它也映射到表值函数。

原因

返回实体的表值函数通常用作帮助器,或封装一个返回实体集合的操作,而不是对整个表进行严格替换。 此更改旨在更符合可能的用户意图。

缓解措施

可在模型配置中显式禁用到表的映射:

modelBuilder.Entity<MyEntity>().ToTable((string)null);

dotnet-ef 面向 .NET 6

跟踪问题 #27787

旧行为

dotnet-ef 命令已面向 .NET Core 3.1 有一段时间了。 这样就可以使用较新版本的工具,而无需安装较新版本的 .NET 运行时。

新行为

在 EF Core 6.0.6 中,dotnet-ef 工具现在以 .NET 6 为目标。 你仍可在面向较旧版本的 .NET 和 .NET Core 的项目上使用该工具,但需要安装 .NET 6 运行时才能运行该工具。

原因

.NET 6.0.200 SDK 更新了 osx-arm64 上 dotnet tool install 的行为,以便为面向 .NET Core 3.1 的工具创建 osx-x64 填充码。 为了维护 dotnet-ef 的工作默认体验,我们必须将其更新为面向 .NET 6。

缓解措施

要在不安装 .NET 6 运行时的情况下运行 dotnet-ef,可以安装旧版工具:

dotnet tool install dotnet-ef --version 3.1.*

可能需要更新IModelCacheKeyFactory实现以处理设计时缓存

跟踪问题 #25154

旧行为

IModelCacheKeyFactory没有选择将设计时模型与运行时模型分开缓存。

新行为

IModelCacheKeyFactory有新的重载,允许设计时模型与运行时模型分开缓存。 不实现此方法可能会导致类似于以下情况的异常:

System.InvalidOperationException:“请求的配置未存储在读取优化模型中,请使用‘DbContext.GetService<IDesignTimeModel>().Model’。”

原因

实现编译的模型需要分离设计时(生成模型时使用)和运行时(执行查询等时使用)。 如果运行时代码需要访问设计时信息,必须缓存设计时模型。

缓解措施

实现新的重载。 例如:

public object Create(DbContext context, bool designTime)
    => context is DynamicContext dynamicContext
        ? (context.GetType(), dynamicContext.UseIntProperty, designTime)
        : (object)context.GetType();

查询时,“Include”中忽略了导航“{navigation}”,因为修复将自动填充它。 如果之后在“Include”中指定了任何导航,则会忽略这些导航。 不允许在 include 树中后退。

跟踪问题 #4315

旧行为

默认情况下,事件 CoreEventId.NavigationBaseIncludeIgnored 记录为警告。

新行为

默认情况下,事件 CoreEventId.NavigationBaseIncludeIgnored 记录为错误,并导致引发异常。

原因

系统不允许使用这些查询模式,因此 EF Core 现在会引发以指示应更新查询。

缓解措施

可以通过将事件配置为警告来还原旧行为。 例如:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Warn(CoreEventId.NavigationBaseIncludeIgnored));