次の方法で共有


EF Core 9 (EF9) での破壊的変更

このページでは、EF Core 8 から EF Core 9 に更新する既存のアプリケーションを破壊する可能性がある API と動作の変更について説明します。 以前のバージョンの EF Core から更新する場合は、以前の破壊的変更について確認するようにしてください。

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

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

まとめ

Note

Azure Cosmos DB を使用している場合、 Azure Cosmos DB の破壊的変更に関する以下の別個のセクションをご覧ください。

重大な変更 影響
EF.Functions.Unhex() により byte[]? が返されるようになりました
SqlFunctionExpression の null 値許容引数のアリティを検証
ToString() メソッドが null インスタンスの空の文字列を返すようになりました
共有フレームワークの依存関係が 9.0.x に更新されました

影響が小さい変更

EF.Functions.Unhex() により byte[]? が返されるようになりました

issue #33864 の追跡

以前の動作

EF.Functions.Unhex() 関数は以前は byte[] を返すよう注釈が付けられていました。

新しい動作

EF Core 9.0 以降、Unhex() には byte[]? を返すように注釈が付けられるようになりました。

理由

Unhex() は SQLite unhex 関数に変換され、無効な入力に対しては NULL が返されます。 その結果、Unhex() は注釈に違反して、これらのケースに対して null を返しました。

軽減策

Unhex() に渡されるテキスト コンテンツが有効な 16 進文字列を表していることが確実な場合、呼び出しで null が返されないというアサーションとして、null を許容する演算子を追加するだけです。

var binaryData = await context.Blogs.Select(b => EF.Functions.Unhex(b.HexString)!).ToListAsync();

それ以外の場合は、Unhex() の戻り値に対して null 値の実行時チェックを追加します。

SqlFunctionExpression の null 値許容引数のアリティを検証

issue #33852 の追跡

以前の動作

以前は、異なる数の引数と null 値許容伝達引数で SqlFunctionExpression を作成することが可能でした。

新しい動作

EF Core 9.0 以降、引数の数と null 値許容伝達引数が一致しない場合、EF はスローを行うようになりました。

理由

引数と null 値許容伝達引数の数が一致しないことで、予期しない動作が発生する可能性があります。

軽減策

argumentsPropagateNullabilityarguments と同じ要素数を持っていることを確認します。 迷った場合は null 値許容引数に対して false を使用します。

ToString() メソッドが null インスタンスの空の文字列を返すようになりました

issue #33941 の追跡

以前の動作

以前は、引数値が null の場合、EF は ToString() メソッドに対して一貫性のない結果を返していました。 たとえば、値が null である bool? プロパティの ToString()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.JsonMicrosoft.Extensions.Caching.MemoryMicrosoft.Extensions.Configuration.AbstractionsMicrosoft.Extensions.Logging および Microsoft.Extensions.DependencyModel などのパッケージを解決するため、通常、これらのアセンブリはアプリと共に展開されません。

新しい動作

EF Core 9.0 は引き続き net8.0 をサポートしていますが、現在は 9.0.x バージョンの System.Text.JsonMicrosoft.Extensions.Caching.MemoryMicrosoft.Extensions.Configuration.AbstractionsMicrosoft.Extensions.Logging および Microsoft.Extensions.DependencyModelを参照しています。 net8.0 をターゲットとするアプリでは、これらのアセンブリのデプロイを回避するために共有フレームワークを利用することはできません。

理由

一致する依存関係バージョンには最新のセキュリティ修正プログラムが含まれており、それらを使用すると EF Core のサービス モデルが簡略化されます。

軽減策

前の動作を取得するには、アプリをターゲット net9.0 に変更します。

Azure Cosmos DB の破壊的変更

9.0 では、Azure Cosmos DB プロバイダーの改善に多大な労力が費やされました。 この変更には、影響が大きい破壊的変更が多数含まれています。既存のアプリケーションをアップグレードする場合、以下をよくお読みください。

重大な変更 影響
ディスクリミネーター プロパティの名前が、Discriminator ではなく $type に変更されました
既定では、id プロパティにディスクリミネーターが含まれるようになりました
Azure Cosmos DB プロバイダー経由の同期 I/O はサポートされなくなりました Medium
SQL クエリで JSON 値を直接プロジェクションする必要があります Medium
未定義の結果がクエリ結果から自動的にフィルター処理されるようになりました Medium
誤って変換されたクエリは変換されなくなりました Medium
無視される代わりに HasIndex がスローされるようになりました
9.0.0-rc.2 の後、IncludeRootDiscriminatorInJsonIdHasRootDiscriminatorInJsonId に名前が変更されました

影響が大きい変更

ディスクリミネーター プロパティの名前が、Discriminator ではなく $type に変更されました

イシュー #34269 の追跡

以前の動作

EF は、JSON ドキュメントにディスクリミネーター プロパティを自動的に追加し、ドキュメントが表すエンティティ型を識別します。 以前のバージョンの EF では、この JSON プロパティには既定で Discriminator という名前が付けられていました。

新しい動作

EF Core 9.0 以降では、ディスクリミネーター プロパティは既定で $type 呼び出されるようになりました。 以前のバージョンの EF から得られた既存のドキュメントが Azure Cosmos DB にある場合、古い Discriminator の命名が使用されており、EF 9.0 にアップグレードした後、それらのドキュメントに対するクエリは失敗します。

理由

新しい JSON プラクティスでは、ドキュメントの種類を識別する必要があるシナリオにおいて、$type プロパティが使用されます。 たとえば、.NET の System.Text.Json もポリモーフィズムをサポートしており、$type を既定の識別子プロパティ名として使用しています (ドキュメント)。 エコシステムの残りの部分に合わせて、外部ツールとの相互運用を容易にするため、既定値が変更されました。

軽減策

最も簡単な軽減策は、以前と同じように、ディスクリミネーター プロパティの名前を Discriminator に構成するだけです。

modelBuilder.Entity<Session>().HasDiscriminator<string>("Discriminator");

これをすべての最上位レベルのエンティティ型に対して行うと、EF は以前と同じように動作します。

この時点で、必要に応じて、新しい $type の命名を使用するようすべてのドキュメントを更新することもできます。

id プロパティには、既定で EF キー プロパティのみ含まれるようになりました

イシュー #34179 の追跡

以前の動作

以前は、EF はエンティティ型のディスクリミネーター値をドキュメントの id プロパティに挿入していました。 たとえば、8 を含む Id プロパティを持つ Blog エンティティ型を保存した場合、JSON id プロパティには Blog|8 が含められます。

新しい動作

EF Core 9.0 以降、JSON id プロパティにはディスクリミネーター値が含められなくなり、キー プロパティの値のみが含められます。 上記の例では、JSON id プロパティは単に 8 になります。 以前のバージョンの EF から得られた既存のドキュメントが Azure Cosmos DB にある場合、JSON id プロパティにはディスクリミネーター値が含まれており、EF 9.0 にアップグレードした後、それらのドキュメントに対するクエリは失敗します。

理由

JSON id プロパティは一意である必要があるため、同じキー値を持つ異なるエンティティが存在できるように、識別子が以前に追加されました。 たとえば、これにより、同じコンテナーとパーティション内に、値 8 を含む Id プロパティを持つ BlogPost の両方を持つことができるようになりました。 これは、各エンティティ型が独自のテーブルにマップされるため、独自のキースペースを持つリレーショナルデータベースデータモデリングパターンにより適しています。

EF 9.0 では、リレーショナル データベースからのユーザーの期待に応えるのではなく、一般的な Azure Cosmos DB NoSQL のプラクティスと期待に沿うようマッピングが全体的に変更されました。 さらに、id プロパティにディスクリミネーターの値があると、外部ツールやシステムが EF で生成された JSON ドキュメントと対話することがより困難になります。このような外部システムは通常、既定で .NET 型から派生する EF ディスクリミネーターの値を認識しません。

軽減策

最も簡単な軽減策は、以前と同様、JSON id プロパティにディスクリミネーターを含むよう EF を構成する方法です。 この目的のため、新しい構成オプションが導入されました。

modelBuilder.Entity<Session>().HasDiscriminatorInJsonId();

これをすべての最上位レベルのエンティティ型に対して行うと、EF は以前と同じように動作します。

この時点で、必要に応じて、すべてのドキュメントを更新して JSON id プロパティを書き換えることもできます。 これは、異なる型のエンティティが同じコンテナー内で同じ ID 値を共有していない場合のみ可能です。

影響が中程度の変更

Azure Cosmos DB プロバイダー経由の同期 I/O はサポートされなくなりました

イシュー #32563 の追跡

以前の動作

以前は、Azure Cosmos DB SDK に対して非同期呼び出しを実行する際に、ToListSaveChanges などの同期メソッドを呼び出すことは、EF Core が .GetAwaiter().GetResult() を使用して同期的にブロックする原因となっていました。 これにより、デッドロックが発生する可能性があります。

新しい動作

EF Core 9.0 以降では、同期 I/O の使用が試みられた場合に、EF が既定で例外をスローするようになりました。 例外メッセージは次のとおりです。"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 SDK ではサポートされていないため、アプリケーションは同期 API の使用を停止するべきです。 例外を抑制する機能は、EF Core の将来のリリースで削除され、その後は非同期 API の使用が唯一の選択肢になります。

SQL クエリで JSON 値を直接プロジェクションする必要があります

イシュー #25527 の追跡

以前の動作

以前は、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 クエリを使用している場合、そのようなクエリには VALUE 修飾子が含まれていないため、EF 9.0 にアップグレードすると、そのクエリが壊れる可能性があります。

理由

各結果を追加の JSON オブジェクトにラップすると、シナリオによってはパフォーマンスが低下して、JSON 結果のペイロードが肥大化する可能性があるため、Azure Cosmos DB を操作する自然な方法ではありません。

軽減策

軽減するには、上に示すように、VALUE 修飾子を SQL クエリのプロジェクションに追加するだけでかまいません。

未定義の結果がクエリ結果から自動的にフィルター処理されるようになりました

イシュー #25527 の追跡

以前の動作

以前は、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 プロパティのいずれかが存在しない場合、クエリは 2 つの結果 (1 つは null) ではなく、1 つの結果のみを返します。

理由

各結果を追加の 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();

誤って変換されたクエリは変換されなくなりました

イシュー #34123 の追跡

以前の動作

以前は、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 がスローされるようになりました

イシュー #34023 の追跡

以前の動作

以前は、HasIndex の呼び出しは EF Cosmos DB プロバイダーによって無視されていました。

新しい動作

HasIndex が指定されている場合、プロバイダーによりスローされるようになりました。

理由

Azure Cosmos DB では、すべてのプロパティに既定でインデックスが付けられます。インデックスを指定する必要はありません。 カスタム インデックス作成ポリシーを定義することはできますが、これは現在 EF ではサポートされていないため、EF のサポートなしで Azure Portal を使用して行うことができます。 HasIndex 呼び出しは何も行われないため、許可されなくなりました。

軽減策

HasIndex の呼び出しをすべて削除します。

9.0.0-rc.2 の後、IncludeRootDiscriminatorInJsonIdHasRootDiscriminatorInJsonId に名前が変更されました

イシュー #34717 の追跡

以前の動作

IncludeRootDiscriminatorInJsonId API は、9.0.0 rc.1 で導入されました。

新しい動作

EF Core 9.0 の最終リリースでは、API の名前が HasRootDiscriminatorInJsonId に変更されました

理由

別の関連 API の名前が Include ではなく Has で始まるように変更されたため、一貫性を保つためにこの API の名前も変更されました。

軽減策

コードで IncludeRootDiscriminatorInJsonId API を使用している場合、代わりに HasRootDiscriminatorInJsonId を参照するよう変更するだけでかまいません。