次の方法で共有


EF Core 10 の破壊的変更 (EF10)

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

概要

Microsoft.Data.Sqlite を使用している場合は、 Microsoft.Data.Sqlite の破壊的変更に関する以下の別のセクションを参照してください。

破壊的変更 影響
EF ツールで、マルチターゲット プロジェクトにフレームワークを指定する必要があるようになった ミディアム
アプリケーション名が接続文字列に挿入されるようになりました
Azure SQL と互換性レベル 170 で既定で使用される SQL Server json データ型
パラメーター化されたコレクションで既定で複数のパラメーターが使用されるようになりました
ExecuteUpdateAsync 、正規表現以外のラムダ を受け入れるようになりました
複合型の列名が一文字化されるようになりました
入れ子になった複合型のプロパティでは、列名に完全なパスが使用されます
IDiscriminatorPropertySetConvention シグネチャが変更されました
IRelationalCommandDiagnosticsLogger メソッドで logCommandText パラメーターを追加する

影響が中程度の変更

EF ツールで、マルチターゲット プロジェクトにフレームワークを指定する必要があるようになった

問題 #37230 の追跡

以前の動作

以前は、EF ツール (dotnet-ef) は、使用するフレームワークを指定せずに、複数のフレームワークを対象とするプロジェクトで使用できました。

新しい動作

EF Core 10.0 以降では、(<TargetFrameworks>ではなく<TargetFramework>を使用して) 複数のフレームワークを対象とするプロジェクトで EF ツールを実行する場合は、--framework オプションで使用するターゲット フレームワークを明示的に指定する必要があります。 このオプションを指定しないと、次のエラーがスローされます。

プロジェクトは複数のフレームワークを対象としています。 --framework オプションを使用して、使用するターゲット フレームワークを指定します。

なぜでしょうか

EF Core 10 では、ツールはプロジェクトの依存関係に関するより正確な情報を取得するために、 ResolvePackageAssets MSBuild タスクに依存し始めました。 ただし、プロジェクトが複数のターゲット フレームワーク (TFM) を対象としている場合、このタスクは使用できません。 このソリューションでは、使用するフレームワークをユーザーが選択する必要があります。

緩和 策

複数のフレームワークを対象とするプロジェクトで EF tools コマンドを実行する場合は、 --framework オプションを使用してターゲット フレームワークを指定します。 例えば次が挙げられます。

dotnet ef migrations add MyMigration --framework net9.0
dotnet ef database update --framework net9.0
dotnet ef migrations script --framework net9.0

プロジェクトファイルが次のようになります。

<PropertyGroup>
  <TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>

EF ツールを実行するときは、フレームワーク ( net9.0 など) のいずれかを選択する必要があります。

影響の少ない変更

アプリケーション名が接続文字列に挿入されるようになりました

問題 #35730 の追跡

新しい動作

Application Nameのない接続文字列が EF に渡されると、EF は、使用されている EF と SqlClient のバージョンに関する匿名情報を含むApplication Nameを挿入するようになりました。 ほとんどの場合、これはアプリケーションに何らかの影響を与えませんが、エッジ ケースによっては動作に影響する可能性があります。 たとえば、EF と別の EF 以外のデータ アクセス テクノロジ (Dapper、ADO.NET など) の両方を使用して同じデータベースに接続する場合、EF は別の更新された接続文字列 ( Application Name が挿入された接続文字列) を使用するため、SqlClient は別の内部接続プールを使用します。 このような混合アクセスが TransactionScope内で行われると、SqlClient が 2 つの異なるデータベースとして識別する 2 つの接続文字列が使用されるため、以前は必要なかった分散トランザクションへのエスカレーションが発生する可能性があります。

緩和 策

軽減策は、接続文字列に Application Name を定義することです。 一度定義すると、EF はそれを上書きせず、元の接続文字列は正確に as-is保持されます。

Azure SQL と互換性レベル 170 で既定で使用される SQL Server json データ型

問題 #36372 の追跡

以前の動作

以前は、プリミティブ コレクションまたは所有型をデータベース内の JSON にマッピングする場合、SQL Server プロバイダーは JSON データを nvarchar(max) 列に格納しました。

public class Blog
{
    // ...

    // Primitive collection, mapped to nvarchar(max) JSON column
    public string[] Tags { get; set; }
    // Owned entity type mapped to nvarchar(max) JSON column
    public List<Post> Posts { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().OwnsMany(b => b.Posts, b => b.ToJson());
}

上記の場合、EF は以前に次の表を生成しました。

CREATE TABLE [Blogs] (
    ...
    [Tags] nvarchar(max),
    [Posts] nvarchar(max)
);

新しい動作

EF 10 では、を使用して EF を構成する (ドキュメントを参照)、または互換性レベルが 170 以上の EF を構成する (ドキュメントを参照) 場合、EF は代わりに新しい JSON データ型にマップされます。

CREATE TABLE [Blogs] (
    ...
    [Tags] json
    [Posts] json
);

今後、新しい JSON データ型を使用して JSON データを SQL Server に格納することをお勧めしますが、 nvarchar(max)から移行するときに動作に違いがあり、一部の特定のクエリ フォームがサポートされない場合があります。 たとえば、SQL Server は JSON 配列に対する DISTINCT 演算子をサポートしていないため、これを試みるクエリは失敗します。

既存のテーブルがあり、 UseAzureSqlを使用している場合、EF 10 にアップグレードすると、既存のすべての nvarchar(max) JSON 列が jsonに変更される移行が生成されることに注意してください。 この変更操作はサポートされており、問題なくシームレスに適用する必要がありますが、データベースの変更は簡単ではありません。

なぜでしょうか

SQL Server によって導入された新しい JSON データ型は、データベースに JSON データを格納して操作するための優れた 1 番目の方法です。特にパフォーマンスが大幅に向上します (ドキュメントを参照)。 Azure SQL Database または SQL Server 2025 を使用するすべてのアプリケーションは、新しい JSON データ型に移行することをお勧めします。

緩和 策

Azure SQL Database をターゲットにしていて、新しい JSON データ型にすぐに移行したくない場合は、互換性レベルが 170 未満の EF を構成できます。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseAzureSql("<connection string>", o => o.UseCompatibilityLevel(160));
}

オンプレミスの SQL Server を対象としている場合、 UseSqlServer の既定の互換性レベルは現在 150 (SQL Server 2019) であるため、JSON データ型は使用されません。

別の方法として、特定のプロパティに対して列の型を明示的に設定して、 nvarchar(max)できます。

public class Blog
{
    public string[] Tags { get; set; }
    public List<Post> Posts { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().PrimitiveCollection(b => b.Tags).HasColumnType("nvarchar(max)");
    modelBuilder.Entity<Blog>().OwnsMany(b => b.Posts, b => b.ToJson().HasColumnType("nvarchar(max)"));
    modelBuilder.Entity<Blog>().ComplexProperty(e => e.Posts, b => b.ToJson());
}

パラメーター化されたコレクションで既定で複数のパラメーターが使用されるようになりました

問題 #34346 の追跡

以前の動作

EF Core 9 以前では、LINQ クエリのパラメーター化されたコレクション ( .Contains()で使用されるコレクションなど) は、既定で JSON 配列パラメーターを使用して SQL に変換されていました。 次のクエリがあるとします。

int[] ids = [1, 2, 3];
var blogs = await context.Blogs.Where(b => ids.Contains(b.Id)).ToListAsync();

SQL Server では、次の SQL が生成されました。

@__ids_0='[1,2,3]'

SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (
    SELECT [i].[value]
    FROM OPENJSON(@__ids_0) WITH ([value] int '$') AS [i]
)

新しい動作

EF Core 10.0 以降では、パラメーター化されたコレクションは、既定で複数のスカラー パラメーターを使用して変換されるようになりました。

SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (@ids1, @ids2, @ids3)

なぜでしょうか

新しい既定の変換では、クエリ プランナーにコレクションに関するカーディナリティ情報が提供されるため、多くのシナリオでクエリ プランが向上する可能性があります。 複数パラメーターアプローチでは、プラン キャッシュの効率 (パラメーター化による) とクエリの最適化 (カーディナリティの提供) のバランスが取られます。

ただし、コレクションのサイズ、クエリ パターン、およびデータベースの特性に応じて、さまざまなワークロードで異なる変換戦略のメリットが得られる場合があります。

新しい既定の変換では、ほとんどの場合、動作の変更やパフォーマンスの低下は発生しませんが、クエリを SQL に変換する方法の変更は、一部のシナリオで悪影響を及ぼす可能性があります。

EF Core 8 または 9 でビルドされ、JSON 配列パラメーター変換のパフォーマンス特性に依存するアプリケーション ( OPENJSON または同様のデータベース固有の関数を使用) では、EF Core 10 にアップグレードするときにパフォーマンスの違いが発生する可能性があります。 これは、前の翻訳戦略の恩恵を受けた大規模なコレクションまたは特定のクエリ パターンを持つクエリに特に関連します。

アップグレード後にパフォーマンスの低下が発生する場合は、以下の軽減策を使用して、以前の動作にグローバルに戻すか、特定のクエリに戻すかを検討してください。

緩和 策

新しい既定の動作 (パフォーマンスの低下など) で問題が発生した場合は、翻訳モードをグローバルに構成できます。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlServer("<CONNECTION STRING>", 
            o => o.UseParameterizedCollectionMode(ParameterTranslationMode.Constant));

使用可能なモードは次のとおりです。

  • ParameterTranslationMode.MultipleParameters - 新しい既定値 (複数のスカラー パラメーター)
  • ParameterTranslationMode.Constant - 値を定数としてインライン化します (EF8 より前の既定の動作)
  • ParameterTranslationMode.Parameter - JSON 配列パラメーターを使用します (EF8-9 の既定値)

クエリごとに翻訳を制御することもできます。

// Use constants instead of parameters for this specific query
var blogs = await context.Blogs
    .Where(b => EF.Constant(ids).Contains(b.Id))
    .ToListAsync();

// Use a single parameter (e.g. JSON parameter with OPENJSON) instead of parameters for this specific query
var blogs = await context.Blogs
    .Where(b => EF.Parameter(ids).Contains(b.Id))
    .ToListAsync();

// Use multiple scalar parameters for this specific query. This is the default in EF 10, but is useful if the default was changed globally:
var blogs = await context.Blogs
    .Where(b => EF.MultipleParameters(ids).Contains(b.Id))
    .ToListAsync();

パラメーター化されたコレクション変換の詳細については、 ドキュメントを参照してください

ExecuteUpdateAsync で、式以外の通常のラムダが受け入れられるようになりました

トラッキングイシュー #32018

以前の動作

以前、ExecuteUpdate は列セッター用の式ツリー引数 (Expression<Func<...>>) を受け入れていました。

新しい動作

EF Core 10.0 以降、ExecuteUpdate は列セッターの非式引数 (Func<...>) を受け入れるようになりました。 列セッター引数を動的に作成するために式ツリーを構築していた場合、コードはコンパイルされなくなりますが、はるかに簡単な代替手段に置き換えることができます (以下を参照)。

なぜでしょうか

列セッター パラメーターが式ツリーであったため、列セッターの動的な構築は非常に困難でした。一部のセッターは、いくつかの条件にのみ基づいて存在します (例については、以下の軽減策を参照してください)。

緩和 策

列セッター引数を動的に作成するために式ツリーを構築していたコードは、書き換える必要がありますが、結果ははるかに簡単になります。 たとえば、ブログのビューを更新し、条件付きで名前も更新するとします。 setters 引数は式ツリーであるため、次のようなコードを記述する必要があります。

// Base setters - update the Views only
Expression<Func<SetPropertyCalls<Blog>, SetPropertyCalls<Blog>>> setters =
    s => s.SetProperty(b => b.Views, 8);

// Conditionally add SetProperty(b => b.Name, "foo") to setters, based on the value of nameChanged
if (nameChanged)
{
    var blogParameter = Expression.Parameter(typeof(Blog), "b");

    setters = Expression.Lambda<Func<SetPropertyCalls<Blog>, SetPropertyCalls<Blog>>>(
        Expression.Call(
            instance: setters.Body,
            methodName: nameof(SetPropertyCalls<Blog>.SetProperty),
            typeArguments: [typeof(string)],
            arguments:
            [
                Expression.Lambda<Func<Blog, string>>(Expression.Property(blogParameter, nameof(Blog.Name)), blogParameter),
                Expression.Constant("foo")
            ]),
        setters.Parameters);
}

await context.Blogs.ExecuteUpdateAsync(setters);

式ツリーを手動で作成することは複雑でエラーが発生しやすく、この一般的なシナリオはこれまでよりもはるかに困難になりました。 EF 10 以降では、代わりに次のコードを記述できるようになりました。

await context.Blogs.ExecuteUpdateAsync(s =>
{
    s.SetProperty(b => b.Views, 8);
    if (nameChanged)
    {
        s.SetProperty(b => b.Name, "foo");
    }
});

複合型の列名が一文字化されるようになりました

問題 #4970 の追跡

以前の動作

以前は、複合型をテーブル列にマッピングするときに、異なる複合型の複数のプロパティに同じ列名がある場合は、同じ列を暗黙的に共有していました。

新しい動作

EF Core 10.0 以降では、同じ名前の列がテーブルに存在する場合、その複合型の列名を一意にするために末尾に数字を付加します。

なぜでしょうか

これにより、複数のプロパティが意図せずに同じ列にマップされた場合に発生する可能性があるデータの破損を防ぐことができます。

緩和 策

同じ列を共有するために複数のプロパティが必要な場合は、 PropertyHasColumnNameを使用して明示的に構成します。

modelBuilder.Entity<Customer>(b =>
{
    b.ComplexProperty(c => c.ShippingAddress, p => p.Property(a => a.Street).HasColumnName("Street"));
    b.ComplexProperty(c => c.BillingAddress, p => p.Property(a => a.Street).HasColumnName("Street"));
});

入れ子になった複合型のプロパティでは、列名に完全なパスが使用されます

以前の動作

以前は、入れ子になった複合型のプロパティは、宣言する型名のみを使用して列にマップされていました。 たとえば、 EntityType.Complex.NestedComplex.Property は列 NestedComplex_Propertyにマップされました。

新しい動作

EF Core 10.0 以降では、入れ子になった複合型のプロパティは、列名の一部としてプロパティへの完全パスを使用します。 たとえば、 EntityType.Complex.NestedComplex.Property が列 Complex_NestedComplex_Propertyにマップされるようになりました。

なぜでしょうか

これにより、列名の一意性が向上し、どのプロパティがどの列にマップされるかが明確になります。

緩和 策

古い列名を維持する必要がある場合は、 PropertyHasColumnNameを使用して明示的に構成します。

modelBuilder.Entity<EntityType>()
    .ComplexProperty(e => e.Complex)
    .ComplexProperty(o => o.NestedComplex)
    .Property(c => c.Property)
    .HasColumnName("NestedComplex_Property");

IDiscriminatorPropertySetConvention シグネチャが変更されました

以前の動作

以前は、 IDiscriminatorPropertySetConvention.ProcessDiscriminatorPropertySet はパラメーターとして IConventionEntityTypeBuilder を受け取りました。

新しい動作

EF Core 10.0 以降では、メソッドシグネチャはIConventionTypeBaseBuilderではなくIConventionEntityTypeBuilderに変更されました。

なぜでしょうか

この変更により、規則はエンティティ型と複合型の両方を操作できます。

緩和 策

新しい署名を使用するようにカスタム規則の実装を更新します。

public virtual void ProcessDiscriminatorPropertySet(
    IConventionTypeBaseBuilder typeBaseBuilder, // Changed from IConventionEntityTypeBuilder
    string name,
    Type type,
    MemberInfo memberInfo,
    IConventionContext<IConventionProperty> context)

IRelationalCommandDiagnosticsLogger メソッドで logCommandText パラメーターを追加する

問題 #35757 の追跡

以前の動作

以前は、IRelationalCommandDiagnosticsLoggerCommandReaderExecutingCommandReaderExecutedなどのCommandScalarExecutingのメソッドは、実行中のデータベース コマンドを表すcommand パラメーターを受け取りました。

新しい動作

EF Core 10.0 以降では、これらのメソッドに追加の logCommandText パラメーターが必要になりました。 このパラメーターには、ログに記録される SQL コマンド テキストが含まれています。このテキストでは、 EnableSensitiveDataLogging が有効になっていない場合に機密データが編集される可能性があります。

なぜでしょうか

この変更により、 インライン定数を既定でログから編集する新機能がサポートされます。 EF がパラメーター値を SQL にインライン化する場合 (たとえば、 EF.Constant()を使用する場合)、機密データ ログが明示的に有効になっていない限り、これらの値はログから編集されるようになりました。 logCommandText パラメーターは、ログ記録のために編集された SQL を提供しますが、command パラメーターには実行される実際の SQL が含まれています。

緩和 策

IRelationalCommandDiagnosticsLoggerのカスタム実装がある場合は、新しい logCommandText パラメーターを含むようにメソッド シグネチャを更新する必要があります。 例えば次が挙げられます。

public InterceptionResult<DbDataReader> CommandReaderExecuting(
    IRelationalConnection connection,
    DbCommand command,
    DbContext context,
    Guid commandId,
    Guid connectionId,
    DateTimeOffset startTime,
    string logCommandText) // New parameter
{
    // Use logCommandText for logging purposes
    // Use command for execution-related logic
}

logCommandText パラメーターには、ログに記録される SQL (インライン定数が修正される可能性があります) が含まれますが、command.CommandTextにはデータベースに対して実行される実際の SQL が含まれます。

Microsoft.Data.Sqlite の破壊的変更

概要

破壊的変更 影響
オフセットなしで GetDateTimeOffset を使用すると、UTC が想定されるようになりました
REAL 列への DateTimeOffset の書き込みが UTC で書き込まれるようになりました
オフセットで GetDateTime を使用すると、UTC で値が返されるようになりました

影響が大きい変更

オフセットなしで GetDateTimeOffset を使用すると、UTC が想定されるようになりました

問題 #36195 の追跡

以前の動作

以前は、オフセット (たとえば、GetDateTimeOffset) を持たないテキスト タイムスタンプに対して2014-04-15 10:47:16を使用する場合、Microsoft.Data.Sqlite は値がローカル タイム ゾーンにあると想定していました。 つまり、値は 2014-04-15 10:47:16+02:00 として解析されました (ローカル タイム ゾーンが UTC+2 であると仮定)。

新しい動作

Microsoft.Data.Sqlite 10.0 以降では、オフセットのないテキスト タイムスタンプで GetDateTimeOffset を使用する場合、Microsoft.Data.Sqlite では値が UTC であると見なされます。

なぜでしょうか

は、オフセットのないタイムスタンプが UTC として扱われる SQLite の動作に合わせて調整することです。

緩和 策

それに応じてコードを調整する必要があります。

最後または一時的な手段として、AppContext スイッチ Microsoft.Data.Sqlite.Pre10TimeZoneHandlingtrueに設定することで、以前の動作に戻すことができます。詳細については、 ライブラリ コンシューマーの AppContext を参照してください。

AppContext.SetSwitch("Microsoft.Data.Sqlite.Pre10TimeZoneHandling", isEnabled: true);

REAL 列への DateTimeOffset の書き込みが UTC で書き込まれるようになりました

問題 #36195 の追跡

以前の動作

以前は、 DateTimeOffset 値を REAL 列に書き込む場合、Microsoft.Data.Sqlite はオフセットを考慮せずに値を書き込みます。

新しい動作

Microsoft.Data.Sqlite 10.0 以降では、 DateTimeOffset 値を REAL 列に書き込むと、変換を実行して書き込む前に、値が UTC に変換されます。

なぜでしょうか

書き込まれた値が正しくなく、REAL タイムスタンプが UTC に変換される SQLite の動作に合わせていません。

緩和 策

それに応じてコードを調整する必要があります。

最後または一時的な手段として、AppContext スイッチ Microsoft.Data.Sqlite.Pre10TimeZoneHandlingtrueに設定することで、以前の動作に戻すことができます。詳細については、 ライブラリ コンシューマーの AppContext を参照してください。

AppContext.SetSwitch("Microsoft.Data.Sqlite.Pre10TimeZoneHandling", isEnabled: true);

オフセットで GetDateTime を使用すると、UTC で値が返されるようになりました

問題 #36195 の追跡

以前の動作

以前は、オフセット (たとえば、GetDateTime) を持つテキスト タイムスタンプに2014-04-15 10:47:16+02:00を使用すると、Microsoft.Data.Sqlite はDateTimeKind.Localを持つ値を返していました (オフセットがローカルでない場合でも)。 オフセットを考慮して時間が正しく解析されました。

新しい動作

Microsoft.Data.Sqlite 10.0 以降では、オフセットを持つテキスト タイムスタンプに GetDateTime を使用すると、Microsoft.Data.Sqlite は値を UTC に変換し、 DateTimeKind.Utcで返します。

なぜでしょうか

時刻が正しく解析された場合でも、コンピューターで構成されたローカル タイム ゾーンに依存していたため、予期しない結果が生じる可能性があります。

緩和 策

それに応じてコードを調整する必要があります。

最後または一時的な手段として、AppContext スイッチ Microsoft.Data.Sqlite.Pre10TimeZoneHandlingtrueに設定することで、以前の動作に戻すことができます。詳細については、 ライブラリ コンシューマーの AppContext を参照してください。

AppContext.SetSwitch("Microsoft.Data.Sqlite.Pre10TimeZoneHandling", isEnabled: true);