EF Core 7.0 (EF7) での破壊的変更

このページでは、EF Core 6 から EF Core 7 に更新する既存のアプリケーションを中断させる可能性がある API と動作の変更について説明します。 以前のバージョンの EF Core から更新する場合は、以前の互換性に影響する変更点を確認してください。

[対象とする Framework]

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

まとめ

重大な変更 影響
SQL Server 接続で、Encrypt の既定値は true になります
一部の警告で、既定で例外が再びスローされます
トリガーまたは特定の計算列を含む SQL Server テーブルで、特別な EF Core 構成が必要になりました
AFTER トリガーおよび仮想テーブルを含む SQLite テーブルでは、特別な EF Core 構成が必要になりました
省略可能なリレーションシップの孤立した依存関係は、自動的に削除されません Medium
SQL Server で TPT マッピングを使用する場合、テーブル間でカスケード削除が構成されます
先行書き込みログを使用しない場合に SQLite でビジー/ロック エラーが発生する可能性が高い
主要なプロパティは、プロバイダー値比較子を使用して構成しなければならない場合があります
チェック制約とその他のテーブル ファセットがテーブルに対して構成されるようになりました
新しいエンティティと、削除されたエンティティとの間のナビゲーションは固定されません
不正なプロバイダーからの FromSqlRaw および関連メソッドを使用すると、例外がスローされます
スキャフォールディングされた OnConfiguringIsConfigured が呼び出されなくなりました

影響が大きい変更

SQL Server 接続で、Encrypt の既定値は true になります

イシュー SqlClient #1210 の追跡

重要

これは、Microsoft.Data.SqlClient パッケージの破壊的変更です。 この変更を元に戻したり軽減したりするために EF Core でできることは、何もありません。 Microsoft.Data.SqlClient GitHub リポジトリにフィードバックを送信してください。その他の質問やヘルプについては、Microsoft サポート Professional にお問い合わせください。

以前の動作

SqlClient 接続文字列では、既定で Encrypt=False が使用されます。 これにより、ローカル サーバーに有効な証明書がない開発用マシン上の接続が可能になります。

新しい動作

SqlClient 接続文字列では、既定で Encrypt=True が使用されます。 これは、次のことを意味します。

  • サーバーは、有効な証明書を使用して構成する必要がある
  • クライアントは、この証明書を信頼する必要がある

これらの条件が満たされない場合、SqlException がスローされます。 例:

サーバーとの接続は正常に確立されましたが、ログイン プロセスでエラーが発生しました。 (プロバイダー:SSL プロバイダー、エラー:0 - この証明書チェーンは、信頼されていない機関によって発行されました。)

理由

既定で、接続がセキュリティで保護されているか、そうでなければアプリケーションが接続に失敗するように、この変更が行われました。

軽減策

次に進むには、次の 3 つの方法があります。

  1. サーバーに有効な証明書をインストールします。 これは関連プロセスであり、証明書を取得し、クライアントによって信頼されている機関によって署名されていることを保証する必要があります。
  2. サーバーに証明書があるが、クライアントによって信頼されていない場合は、TrustServerCertificate=True を使用して通常の信頼メカニズムをバイパスできるようにします。
  3. 接続文字列に Encrypt=False を明示的に追加します。

警告

オプション 2 とオプション 3 はどちらも、サーバーが安全でない可能性がある状態のままです。

一部の警告で、既定で例外が再びスローされます

イシュー #29069 の追跡

以前の動作

EF Core 6.0 では、SQL Server プロバイダーにバグがあると、既定で例外をスローするように構成された一部の警告が、例外をスローするのではなく、ログに記録されていました。 これらの警告は次のとおりです。

EventId 説明
RelationalEventId.AmbientTransactionWarning アプリケーションでは、アンビエント トランザクションが使用されると予期されていたが、実際には無視された可能性があります。
RelationalEventId.IndexPropertiesBothMappedAndNotMappedToTable インデックスにより、テーブルの列にマッピングされるプロパティとマッピングされないプロパティが指定されます。
RelationalEventId.IndexPropertiesMappedToNonOverlappingTables インデックスにより、重複しないテーブルの列にマップされるプロパティが指定されます。
RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables 外部キーにより、関連テーブルにマップされないプロパティが指定されます。

新しい動作

EF Core 7.0 以降では、これらの警告によって既定で例外が再びスローされます。

理由

これらは、修正する必要があるアプリケーション コードのエラーを示す可能性が非常に高い問題です。

軽減策

警告の原因となっている根本的な問題を修正します。

または、単にログに記録されるか、または完全に抑制されるように、警告レベルを変更できます。 例:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Ignore(RelationalEventId.AmbientTransactionWarning));

トリガーまたは特定の計算列を含む SQL Server テーブルで、特別な EF Core 構成が必要になりました

イシュー #27372 の追跡

以前の動作

以前のバージョンの SQL Server プロバイダーでは、効率の低い手法を使用して変更が保存され、これは常に機能していました。

新しい動作

EF Core では、既定で、格段に効率のよい手法を使用して変更が保存されるようになりました。ただし、ターゲット テーブルにデータベース トリガー、または特定の種類の計算列が含まれる場合、この手法は SQL Server ではサポートされません。 詳細については、SQL Server のドキュメントを参照してください。

理由

新しいメソッドにリンクされているパフォーマンスの向上は重大であるため、既定でユーザーに提供する必要があります。 同時に、EF Core アプリケーションでのデータベース トリガーまたは影響を受ける計算列の使用量は少ないため、破壊的変更による悪影響よりもパフォーマンスの向上により得るものの方が多いと見積もっています。

軽減策

EF Core 8.0 以降では、"OUTPUT" 句を使用するかどうかを明示的に構成できます。 次に例を示します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable(tb => tb.UseSqlOutputClause(false));
}

EF7 以降では、ターゲット テーブルにトリガーがある場合、これを EF Core に知らせることができます。そうすると、EF は以前の効率性の低い手法に戻ります。 これを行うには、対応するエンティティ型を次のように構成します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable(tb => tb.HasTrigger("SomeTrigger"));
}

これにより、実際に EF Core でトリガーが作成または管理されるわけではありません。現時点では、トリガーがテーブルに存在することが EF Core に通知されるだけです。 したがって、任意のトリガー名を使用できます。 "実際にテーブルにトリガーがない場合でも"、トリガーを指定すると、以前の動作に戻すことができます。

ほとんど、またはすべてのテーブルにトリガーがある場合は、次のモデル構築規則を使用して、すべてのモデルのテーブルに対する新しい効率的な手法の使用をオプトアウトできます。

public class BlankTriggerAddingConvention : IModelFinalizingConvention
{
    public virtual void ProcessModelFinalizing(
        IConventionModelBuilder modelBuilder,
        IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
        {
            var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
            if (table != null
                && entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(table.Value) == null)
                && (entityType.BaseType == null
                    || entityType.GetMappingStrategy() != RelationalAnnotationNames.TphMappingStrategy))
            {
                entityType.Builder.HasTrigger(table.Value.Name + "_Trigger");
            }

            foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table))
            {
                if (entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(fragment.StoreObject) == null))
                {
                    entityType.Builder.HasTrigger(fragment.StoreObject.Name + "_Trigger");
                }
            }
        }
    }
}

ConfigureConventions をオーバーライドして、DbContext に対してこの規則を使用します。

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Add(_ => new BlankTriggerAddingConvention());
}

これにより、すべてのモデルのテーブルで HasTrigger が効率的に呼び出されるため、各テーブルに対して手動で行う必要はありません。

AFTER トリガーおよび仮想テーブルを含む SQLite テーブルでは、特別な EF Core 構成が必要になりました

イシュー #29916 の追跡

以前の動作

以前のバージョンの SQLite プロバイダーでは、効率の低い手法を使用して変更が保存され、これは常に機能していました。

新しい動作

EF Core は、既定では、RETURNING 句を使って、より効率的な手法で変更を保存するようになりました。 残念ながら、ターゲット テーブルにデータベース AFTER トリガーがある場合、ターゲット テーブルが仮想テーブルである場合、または古いバージョンの SQLite が使われている場合、この手法は SQLite ではサポートされません。 詳しくは、SQLite のドキュメントをご覧ください。

理由

新しいメソッドによってもたらされる簡素化とパフォーマンスの向上は十分大きいため、既定でユーザーに提供することが重要です。 同時に、EF Core アプリケーションでデータベース トリガーと仮想テーブルを使うことは少なく、このパフォーマンス向上は破壊的変化による負の影響を上回るものと思われます。

軽減策

EF Core 8.0 では、以前の効率性の低い SQL に明示的に戻すために UseSqlReturningClause メソッドが導入されました。例: 次に例を示します。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable(tb => tb.UseSqlReturningClause(false));
}

まだ EF Core 7.0 を使用している場合は、コンテキスト構成に次のコードを挿入することで、アプリケーション全体を古いメカニズムに戻すことができます。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlite(...)
        .ReplaceService<IUpdateSqlGenerator, SqliteLegacyUpdateSqlGenerator>();

影響が中程度の変更

省略可能なリレーションシップの孤立した依存関係は、自動的に削除されません

イシュー #27217 の追跡

以前の動作

リレーションシップの外部キーが null 許容の場合、リレーションシップは省略可能です。 外部キーを null に設定すると、依存エンティティは関連プリンシパル エンティティなしで存在できます。 省略可能なリレーションシップは、カスケード削除を使用するように構成できますが、これは既定ではありません。

オプションの依存オブジェクトは、外部キーを null に設定するか、またはその外部キーとの間のナビゲーションをクリアすることで、プリンシパルから切断できます。 EF Core 6.0 では、リレーションシップがカスケード削除用に構成されている場合、依存関係が削除されます。

新しい動作

EF Core 7.0 以降では、依存関係は削除されなくなりました。 プリンシパルが削除された場合でも、リレーションシップに対してカスケード削除が構成されているため、依存関係は引き続き削除されます。

理由

依存関係はプリンシパルとのリレーションシップなしで存在できるため、リレーションシップを切断してもエンティティは削除されません。

軽減策

依存関係を明示的に削除できます。

context.Remove(blog);

あるいは、SaveChanges をオーバーライドまたはインターセプトして、プリンシパル参照のない依存関係を削除することもできます。 例:

context.SavingChanges += (c, _) =>
    {
        foreach (var entry in ((DbContext)c!).ChangeTracker
            .Entries<Blog>()
            .Where(e => e.State == EntityState.Modified))
        {
            if (entry.Reference(e => e.Author).CurrentValue == null)
            {
                entry.State = EntityState.Deleted;
            }
        }
    };

SQL Server で TPT マッピングを使用する場合、テーブル間でカスケード削除が構成されます

イシュー #28532 の追跡

以前の動作

TPT 戦略を使用して継承階層をマッピングする場合、ベース テーブルには、そのエンティティの実際の型に関係なく、保存されたすべてのエンティティの行が含まれている必要があります。 ベース テーブルの行を削除すると、他のすべてのテーブルの行が削除されます。 EF Core では、これに対してカスケード削除が構成されます。

EF Core 6.0 では、SQL Server データベース プロバイダーにバグがあると、これらのカスケード削除が作成されませんでした。

新しい動作

EF Core 7.0 以降では、他のデータベースの場合と同様に、SQL Server に対してカスケード削除が作成されるようになりました。

理由

ベース テーブルから TPT のサブテーブルへのカスケード削除では、ベース テーブル内の行を削除することでエンティティを削除できます。

軽減策

ほとんどの場合、この変更によって問題が発生することはありません。 ただし、テーブル間で複数のカスケード動作が構成されている場合、SQL Server は大きく制限されます。 つまり、TPT マッピング内のテーブル間に既存のカスケード リレーションシップがある場合、SQL Server では次のエラーが生成される可能性があります。

Microsoft.Data.SqlClient.SqlException: DELETE ステップは REFERENCE 制約 "FK_Blogs_People_OwnerId" と競合しています。 競合が発生したのは、データベース "Scratch"、テーブル "dbo.Blogs"、列 'OwnerId' です。 ステートメントは終了されました。

たとえば、このモデルでは、カスケード リレーションシップのサイクルが作成されます。

[Table("FeaturedPosts")]
public class FeaturedPost : Post
{
    public int ReferencePostId { get; set; }
    public Post ReferencePost { get; set; } = null!;
}

[Table("Posts")]
public class Post
{
    public int Id { get; set; }
    public string? Title { get; set; }
    public string? Content { get; set; }
}

これらの 1 つは、サーバーでカスケード削除を使用しないように構成する必要があります。 たとえば、明示的なリレーションシップを変更するには、次のようにします。

modelBuilder
    .Entity<FeaturedPost>()
    .HasOne(e => e.ReferencePost)
    .WithMany()
    .OnDelete(DeleteBehavior.ClientCascade);

または、TPT マッピング用に作成された暗黙的なリレーションシップを変更するには、次のようにします。

modelBuilder
    .Entity<FeaturedPost>()
    .HasOne<Post>()
    .WithOne()
    .HasForeignKey<FeaturedPost>(e => e.Id)
    .OnDelete(DeleteBehavior.ClientCascade);

先行書き込みログを使用しない場合に SQLite でビジー/ロック エラーが発生する可能性が高い

以前の動作

以前のバージョンの SQLite プロバイダーでは、テーブルがロック/ビジー状態で、先行書き込みログ (WAL) が有効になっていないときに自動的な再試行が可能な効率の低い手法を使用して変更を保存していました。

新しい動作

EF Core は、既定では、RETURNING 句を使って、より効率的な手法で変更を保存するようになりました。 残念ながら、この手法は、ビジー/ロック時に自動的に再試行することはできません。 先行書き込みログを使用しないマルチスレッド アプリケーション (Web アプリなど) では、これらのエラーが発生するのが一般的です。

理由

新しいメソッドによってもたらされる簡素化とパフォーマンスの向上は十分大きいため、既定でユーザーに提供することが重要です。 EF Core によって作成されたデータベースでは、既定で先行書き込みログも有効になります。 SQLite チームは、既定で先行書き込みログを有効にすることも推奨しています。

軽減策

可能であれば、データベースで先行書き込みログを有効にしてください。 データベースが EF によって作成された場合は、既にそうなっているはずです。 有効でない場合は、次のコマンドを実行して先行書き込みログを有効にすることができます。

PRAGMA journal_mode = 'wal';

何らかの理由で先行書き込みログを有効にできない場合は、コンテキスト構成に次のコードを挿入することで、アプリケーション全体を古いメカニズムに戻すことができます。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlite(...)
        .ReplaceService<IUpdateSqlGenerator, SqliteLegacyUpdateSqlGenerator>();

影響が小さい変更

主要なプロパティは、プロバイダー値比較子を使用して構成しなければならない場合があります

イシュー #27738 の追跡

以前の動作

EF Core 6.0 では、エンティティ型のプロパティから直接取得されたキー値が、変更の保存時にキー値の比較に使用されていました。 これにより、これらのプロパティに対して構成されているカスタム値比較子が使用されます。

新しい動作

EF Core 7.0 以降では、これらの比較にデータベース値が使用されます。 これは、ほとんどのケースで "機能" します。 ただし、プロパティでカスタム比較子が使用されていて、その比較子をデータベース値に適用できない場合は、次に示すように "プロバイダー値比較子" が必要になることがあります。

理由

さまざまなエンティティ分割やテーブル分割により、複数のプロパティが同じデータベース列にマップされたり、その逆の状態が生じたりする可能性があります。 そのため、データベースで使用される値に変換した後に値を比較する必要があります。

軽減策

プロバイダー値比較子を構成します。 たとえば、値オブジェクトがキーとして使用されていて、そのキーの比較子で大文字と小文字が区別されない文字列比較が使用されている場合を考えてみます。

var blogKeyComparer = new ValueComparer<BlogKey>(
    (l, r) => string.Equals(l.Id, r.Id, StringComparison.OrdinalIgnoreCase),
    v => v.Id.ToUpper().GetHashCode(),
    v => v);

var blogKeyConverter = new ValueConverter<BlogKey, string>(
    v => v.Id,
    v => new BlogKey(v));

modelBuilder.Entity<Blog>()
    .Property(e => e.Id).HasConversion(
        blogKeyConverter, blogKeyComparer);

データベース値 (文字列) では、BlogKey 型に対して定義されている比較子を直接使用することはできません。 したがって、大文字と小文字が区別されない文字列比較用のプロバイダー比較子を構成する必要があります。

var caseInsensitiveComparer = new ValueComparer<string>(
    (l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
    v => v.ToUpper().GetHashCode(),
    v => v);

var blogKeyComparer = new ValueComparer<BlogKey>(
    (l, r) => string.Equals(l.Id, r.Id, StringComparison.OrdinalIgnoreCase),
    v => v.Id.ToUpper().GetHashCode(),
    v => v);

var blogKeyConverter = new ValueConverter<BlogKey, string>(
    v => v.Id,
    v => new BlogKey(v));

modelBuilder.Entity<Blog>()
    .Property(e => e.Id).HasConversion(
        blogKeyConverter, blogKeyComparer, caseInsensitiveComparer);

チェック制約とその他のテーブル ファセットがテーブルに対して構成されるようになりました

イシュー #28205 の追跡

以前の動作

EF Core 6.0 では、HasCheckConstraintHasCommentIsMemoryOptimized はエンティティ型ビルダーで直接呼び出されていました。 次に例を示します。

modelBuilder
    .Entity<Blog>()
    .HasCheckConstraint("CK_Blog_TooFewBits", "Id > 1023");

modelBuilder
    .Entity<Blog>()
    .HasComment("It's my table, and I'll delete it if I want to.");

modelBuilder
    .Entity<Blog>()
    .IsMemoryOptimized();

新しい動作

EF Core 7.0 以降では、これらのメソッドは代わりにテーブル ビルダーで呼び出されます。

modelBuilder
    .Entity<Blog>()
    .ToTable(b => b.HasCheckConstraint("CK_Blog_TooFewBits", "Id > 1023"));

modelBuilder
    .Entity<Blog>()
    .ToTable(b => b.HasComment("It's my table, and I'll delete it if I want to."));

modelBuilder
    .Entity<Blog>()
    .ToTable(b => b.IsMemoryOptimized());

既存のメソッドは、Obsolete としてマークされています。 現時点では、これらは新しいメソッドと同じ動作ですが、今後のリリースで削除される予定です。

理由

これらのファセットはテーブルにのみ適用されます。 マップされたビュー、関数、またはストアド プロシージャには適用されません。

軽減策

上に示すように、テーブル ビルダー メソッドを使用します。

イシュー #28249 の追跡

以前の動作

EF Core 6.0 では、追跡クエリから、または DbContextアタッチすることで新しいエンティティを追跡する場合、その Deleted 状態の関連エンティティとの間のナビゲーションが固定されます

新しい動作

EF Core 7.0 以降では、Deleted エンティティとの間のナビゲーションは固定されません。

理由

エンティティが Deleted としてマークされた後、削除されていないエンティティに関連付けてもほとんど意味がありません。

軽減策

エンティティを Deleted としてマークする前にエンティティをクエリまたはアタッチするか、または削除されたエンティティとの間でナビゲーション プロパティを手動で設定します。

イシュー #26502 の追跡

以前の動作

EF Core 6.0 では、リレーショナル プロバイダーの使用時に Azure Cosmos DB FromSqlRaw 拡張メソッドを使用するか、または Azure Cosmos DB プロバイダーの使用時にリレーショナル FromSqlRaw 拡張メソッドを使用すると、エラーを返さずに失敗する可能性がありました。 同様に、メモリ内プロバイダーでリレーショナル メソッドを使用することは、サイレント no-op です。

新しい動作

EF Core 7.0 以降では、あるプロバイダー用に設計された拡張メソッドを別のプロバイダーで使用すると、例外がスローされます。

理由

すべての状況で正しく機能するには、適切な拡張メソッドを使用する必要があります。

軽減策

使用しているプロバイダーに対して適切な拡張メソッドを使用します。 複数のプロバイダーが参照されている場合は、拡張メソッドを静的メソッドとして呼び出します。 次に例を示します。

var result = CosmosQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToList();

または:

var result = RelationalQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToList();

スキャフォールディングされた OnConfiguringIsConfigured が呼び出されなくなりました

イシュー #4274 の追跡

以前の動作

EF Core 6.0 では、既存のデータベースからスキャフォールディングされた DbContext 型に、IsConfigured への呼び出しが含まれていました。 次に例を示します。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
        optionsBuilder.UseNpgsql("MySecretConnectionString");
    }
}

新しい動作

EF Core 7.0 以降では、IsConfigured の呼び出しは含まれなくなりました。

理由

非常に限られたシナリオで、データベース プロバイダーが DbContext 内で構成されることがありますが、これはコンテキストがまだ構成されていない場合だけです。 代わりに、ここに OnConfiguring を残しておくと、コンパイル時の警告にもかかわらず、機密情報を含む接続文字列がコードに残される可能性が高くなります。 したがって、特に --no-onconfiguring (.NET CLI) または -NoOnConfiguring (Visual Studio パッケージ マネージャー コンソール) フラグを使用して OnConfiguring メソッドのスキャフォールディングを防ぐことができ、本当に必要な場合に IsConfigured を追加するためのカスタマイズ可能なテンプレートが存在することを考えると、これを削除して、より安全でクリーンなコードにすることは価値があると考えられました。

軽減策

次のいずれか:

  • 既存のデータベースからスキャフォールディングする場合は、--no-onconfiguring (.NET CLI) または -NoOnConfiguring (Visual Studio パッケージ マネージャー コンソール) 引数を使います。
  • IsConfigured の呼び出しを追加して戻すには、T4 テンプレートをカスタマイズします。