EF Core 6.0 での破壊的変更

次の API と動作の変更により、EF Core 6.0 に更新されている既存のアプリケーションが破損する可能性があります。

ターゲット フレームワーク

EF Core 6.0 は .NET 6 をターゲットとします。 以前の .NET、.NET Core、および .NET Framework バージョンをターゲットとするアプリケーションは、EF Core 6.0 を使用するために .NET 6 をターゲットとする必要があります。

まとめ

重大な変更 影響
テーブルを共有し、必須のプロパティがない、入れ子になった省略可能な依存を保存できません
所有エンティティの所有者を変更すると、例外がスローされるようになりました
Azure Cosmos DB: 関連するエンティティ型が所有として検出される
SQLite: 接続がプールされます
マップされた結合エンティティがない多対多リレーションシップは、スキャフォールディングされるようになりました
DeleteBehavior と ON DELETE 値の間のマッピングのクリーンアップ
インメモリ データベースで必須のプロパティに null が含まれていないことが検証されます
コレクションへの参加時の最後の ORDER BY の削除
DbSet で IAsyncEnumerable が実装されなくなりました
TVF 戻りエンティティ型も既定でテーブルにマップされます
CHECK 制約名の一意性が検証されるようになりました
IReadOnly メタデータ インターフェイスを追加し、拡張メソッドを削除しました
IExecutionStrategy がシングルトン サービスになりました
SQL Server: より多くのエラーが一時的なものと見なされます
Azure Cosmos DB: 'id' 値でより多くの文字がエスケープされる
一部の Singleton サービスが Scoped となりました 低*
サービスを追加または置き換える拡張機能の新しいキャッシュ API 低*
新しいスナップショットとデザイン時モデルの初期化手順
OwnedNavigationBuilder.HasIndex から別の型が返されるようになりました
DbFunctionBuilder.HasSchema(null) によって [DbFunction(Schema = "schema")] がオーバーライドされます
事前に初期化されたナビゲーションはデータベース クエリの値によってオーバーライドされます
データベース内の不明な列挙型文字列値はクエリ時に列挙型の既定値に変換されません
DbFunctionBuilder.HasTranslation の関数の引数は IReadOnlyCollection ではなく IReadOnlyList として提供されるようになりました
エンティティがテーブル値関数にマップされても既定のテーブル マッピングは削除されません
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; }
}

ContactInfo または Address のプロパティはいずれも必須ではなく、これらのエンティティ型はすべて同じテーブルにマップされています。 (必須の依存とは対照的に) 省略可能な依存の規則で、ContactInfo のすべての列が null の場合、所有者 Customer に対するクエリ時に ContactInfo のインスタンスは作成されません。 ただし、これは Address の列が非 null であっても Address のインスタンスが作成されないことも意味します。

新しい動作

このモデルを使おうとすると、次のような例外が発生します。

System.InvalidOperationException: Entity type 'ContactInfo' is an optional dependent using table sharing and containing other dependents without any required non shared property to identify whether the entity exists. (エンティティ型 "ContactInfo" は、テーブル共有を使用し、エンティティが存在するかどうかを識別するために必要な非共有プロパティを備えない他の依存を含む、オプションの依存です。) If all nullable properties contain a null value in database then an object instance won't be created in the query causing nested dependent's values to be lost. (データベースですべての Null 許容プロパティに null 値が含まれている場合、クエリでオブジェクト インスタンスが作成されないため、入れ子になった依存の値は失われてしまいます。) Add a required property to create instances with null values for other properties or mark the incoming navigation as required to always create an instance. (他のプロパティ用に null 値を含むインスタンスを作成するために必要なプロパティを追加するか、常にインスタンスが作成されるように受信ナビゲーションを必須としてマークします。)

このようにして、データのクエリ時や保存時にデータが失われるのを防ぎます。

理由

テーブルを共有し、必須のプロパティがない、入れ子になった省略可能な依存があるモデルを使うと、エラーなしでデータが失われることがよくあります。

軽減策

テーブルを共有し、必須のプロパティがない、省略可能な依存は使わないでください。 これには、3 つの簡単な方法があります。

  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. 依存に少なくとも 1 つの必須のプロパティが含まれるようにします。

  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 では引き続き、主キーとして使用されるシャドウ プロパティと、所有者を指す外部キーが作成されます。 所有者エンティティが変更された場合、所有エンティティの外部キーの値が変更されます。また、主キーとしても使用されるので、エンティティ ID が変更されます。 これは EF Core ではまだ完全にはサポートされていません。また、所有エンティティに対して条件付きでのみ許可されていたため、内部状態が不整合になる場合がありました。

軽減策

同じ所有インスタンスを新しい所有者に割り当てる代わりに、コピーを割り当て、古いインスタンスを削除できます。

イシュー #24803 の追跡新機能: 既定で暗黙的な所有権に設定される

以前の動作

他のプロバイダーと同様に、関連するエンティティ型は通常の (非所有) 型として検出されました。

新しい動作

関連するエンティティ型は、検出されたエンティティ型によって所有されるようになります。 DbSet<TEntity> プロパティに対応するエンティティ型のみが非所有として検出されます。

理由

この動作では、関連データを 1 つのドキュメントに埋め込む際に、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 とエンティティ型をスキャフォールディング (リバース エンジニアリング) すると、多対多リレーションシップの場合、結合テーブルが結合エンティティ型に常に明示的にマップされていました。

新しい動作

他のテーブルへの 2 つの外部キー プロパティのみを含む単純な結合テーブルは、明示的なエンティティ型にマップされなくなりましたが、代わりに 2 つの結合テーブル間の多対多リレーションシップとしてマップされるようになりました。

理由

明示的な結合の種類がない多対多リレーションシップは EF Core 5.0 で導入されたものであり、単純な結合テーブルをよりすっきりと、より自然に表現する方法です。

軽減策

2 つの軽減策があります。 推奨される方法は、多対多リレーションシップを直接使うようにコードを更新することです。 多対多リレーションシップのために 2 つの外部キーしか含まれていない場合、結合エンティティ型を直接使う必要があることは非常にまれです。

または、明示的な結合エンティティを 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
Cascasde CASCADE
ClientCascade RESTRICTNO ACTION
SetNull SET NULL
ClientSetNull RESTRICTNO ACTION

スキャフォールディングの変更点は次のとおりです。

ON DELETE OnDelete()
NO ACTION ClientSetNull
RESTRICT ClientSetNullRestrict
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 値の保存が許可されていました。

新しい動作

SaveChanges または SaveChangesAsync が呼び出され、必須のプロパティが null に設定されていると、インメモリ データベースから Microsoft.EntityFrameworkCore.DbUpdateException がスローされます。

理由

インメモリ データベースの動作が、他のデータベースの動作と一致するようになりました。

軽減策

以前の動作 (つまり、null 値を確認しない) は、インメモリ プロバイダーを構成するときに復元できます。 次に例を示します。

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

コレクションへの参加時の最後の ORDER BY の削除

イシュー #19828 の追跡

以前の動作

コレクションで SQL JOIN を実行する場合は (一対多リレーションシップ)、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 コンストラクトを介してその直接列挙ができるようにするためでした。 残念ながら、非同期 LINQ 演算子をクライアント側で作成するためにプロジェクトが System.Linq.Async も参照している場合、IQueryable<T> で定義された演算子と IAsyncEnumerable<T> で定義された演算子の間で、あいまいな呼び出しエラーが発生していました。 C# 9 では、foreach ループに対する拡張機能 GetEnumerator のサポートが追加され、IAsyncEnumerable を参照する元の主な理由がなくなりました。

DbSet の使用法の大部分は、DbSet で LINQ 演算子を作成したり、それを列挙したりすることなので、そのまま引き続き機能します。機能しない使用法は、DbSetIAsyncEnumerable に直接キャストしようとするものだけです。

軽減策

DbSet<TEntity>IAsyncEnumerable<T> として参照する必要がある場合は、DbSet<TEntity>.AsAsyncEnumerable を呼び出して明示的にキャストします。

TVF 戻りエンティティ型も既定でテーブルにマップされます

イシュー #23408 の追跡

以前の動作

HasDbFunction で構成された TVF の戻り値の型として使用される場合、エンティティ型は既定ではテーブルにマップされませんでした。

新しい動作

TVF の戻り値の型として使用されるエンティティ型では、既定のテーブル マッピングが保持されます。

理由

TVF を構成すると、戻りエンティティ型の既定のテーブル マッピングが削除されることは直感的ではありません。

軽減策

既定のテーブル マッピングを削除するには、ToTable(EntityTypeBuilder, String) を呼び出します。

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

CHECK 制約名の一意性が検証されるようになりました

イシュー #25061 の追跡

以前の動作

同じテーブルでの同じ名前の CHECK 制約の宣言と使用が許可されていました。

新しい動作

同じテーブルで同じ名前の 2 つの CHECK 制約を明示的に構成すると、例外が発生するようになります。 規則によって作成される CHECK 制約には、一意の名前が割り当てられます。

理由

ほとんどのデータベースでは、同じ名前の 2 つの CHECK 制約を同じテーブルに作成することはできません。また、テーブル全体でも一意である必要がある場合があります。 そのため、移行の適用時に例外がスローされます。

軽減策

場合によっては、この変更により、有効な CHECK 制約名が異なることがあります。 目的の名前を明示的に指定するには、HasName を呼び出します。

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

IReadOnly メタデータ インターフェイスを追加し、拡張メソッドを削除しました

イシュー #19213 の追跡

以前の動作

拡張メソッドだけでなく、IModelIMutableModel および IConventionModel というメタデータ インターフェイスの 3 つのセットがありました。

新しい動作

新しい IReadOnly インターフェイスのセット (IReadOnlyModel など) が追加されました。 メタデータ インターフェイスに対して以前に定義された拡張メソッドは、既定のインターフェイス メソッドに変換されました。

理由

既定のインターフェイス メソッドでは実装をオーバーライドできます。これは、パフォーマンスを向上させるために新しい実行時モデルを実装することによって利用されます。

軽減策

これらの変更はほとんどのコードに影響しないはずです。 しかし、静的呼び出し構文を介して拡張メソッドを使用していた場合は、インスタンス呼び出し構文に変換する必要があります。

IExecutionStrategy がシングルトン サービスになりました

イシュー #21350 の追跡

新しい動作

IExecutionStrategy は、シングルトン サービスになりました。 これは、カスタム実装で追加されたすべての状態は後続の実行に残り、ExecutionStrategy に渡されたデリゲートは一度だけ実行されることを意味します。

理由

これにより、EF の 2 つのホット パスに対する割り当てが減少しました。

軽減策

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 サービスが Scoped となりました

イシュー #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 ではモデルの終了処理手順を抽象化するため、ユーザーに対してさらに破壊的変更を行うことなく変更できるようになりました。

最適化された実行時モデルが導入され、実行時のパフォーマンスが向上しました。 いくつかの最適化が行われ、そのうちの 1 つでは実行時に使用されないメタデータが削除されます。

軽減策

次のスニペットは、現在のモデルがスナップショット モデルと異なるかどうかを確認する方法を示しています。

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());

このスニペットは、外部でモデルを作成し、IDesignTimeDbContextFactory<TContext> を呼び出すことによって、UseModel を実装する方法を示しています。

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; }
}

Bar を含む Foo に対する追跡なしのクエリにより、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 が出力されます。

理由

この変更を加える理由は 2 つあります。

  1. 追跡クエリと追跡なしのクエリを一貫した動作にするため。
  2. データベースのクエリを実行する場合、アプリケーション コードを使ってデータベースに格納されている値を取得することを想定するのは合理的です。

軽減策

2 つの軽減策があります。

  1. 結果に含めるべきではないデータベースのオブジェクトのクエリを実行しないでください。 たとえば、上のコード スニペットでは、Bar インスタンスがデータベースから返され、結果に含まれるべきではない場合、IncludeFoo.Bar を実行しないでください。
  2. データベースからクエリを実行した後に、ナビゲーションの値を設定します。 たとえば、上のコード スニペットでは、クエリを実行した後に foo.Bar = new() を呼び出します。

また、関連するエンティティ インスタンスを既定のオブジェクトに初期化しないことを検討してください。 これは、関連するインスタンスが新しいエンティティであり、データベースに保存されておらず、キー値も設定されていないことを意味します。 代わりに関連するエンティティがデータベースに存在する場合、コード内のデータはデータベースの格納データと根本的に一致しません。

データベース内の不明な列挙型文字列値はクエリ時に列挙型の既定値に変換されません

イシュー #24084 の追跡

以前の動作

列挙型プロパティは、HasConversion<string>() または EnumToStringConverter を使ってデータベース内の文字列型の列にマップできます。 この結果、EF Core によって列の文字列値は .NET 列挙型の一致するメンバーに変換されます。 ただし、文字列値が列挙型のメンバーと一致しない場合は、プロパティに列挙型の既定値が設定されました。

新しい動作

EF Core 6.0 では、"Cannot convert string value '{value}' from the database to any value in the mapped '{enumType}' enum" (データベースの文字列値 '{value}' をマップされた '{enumType}' 列挙型の任意の値に変換できません) というメッセージと共に InvalidOperationException がスローされるようになりました。

理由

既定値に変換すると、後でエンティティをデータベースに保存し直した場合に、データベースが破損する可能性があります。

軽減策

理想的には、データベースの列には有効な値のみを含めるようにします。 または、以前の動作を持つ ValueConverter を実装します。

DbFunctionBuilder.HasTranslation の関数の引数は IReadOnlyCollection ではなく IReadOnlyList として提供されるようになりました

イシュー #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 shim が作成されました。 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();

ナビゲーション '{navigation}' は、修正によって自動的に入力されるため、クエリの 'Include' から無視されていました。 その後、'Include' でさらにナビゲーションが指定された場合、それらは無視されます。 インクルード ツリーに戻ることは許可されません。

イシュー #4315 の追跡

以前の動作

イベント CoreEventId.NavigationBaseIncludeIgnored は、既定で警告としてログに記録されていました。

新しい動作

イベント CoreEventId.NavigationBaseIncludeIgnored は既定でエラーとしてログに記録され、例外がスローされます。

理由

これらのクエリ パターンは許可されていないため、EF Core はクエリを更新する必要があることを示すためにスローするようになりました。

軽減策

イベントを警告として設定することで、以前の動作を復元できます。 次に例を示します。

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