bucket-sort logo bucket-sort

プログラミングとインフラエンジニアリングの覚え書き

  • Posts
  • About
  • Contact
  1. Home
  2. All Posts
  3. [C#] EF Core の実務向け機能を理解する

[C#] EF Core の実務向け機能を理解する

Aug 3, 2026 C# , .NET , Entity Framework Core bucket-sort

前回は、グローバルフィルター、生 SQL、投影、データベース生成値など、問い合わせ周辺の便利機能を見ました。

今回は、より実務寄りの機能を扱います。
同時実行制御、接続回復、値変換、シャドウプロパティ、SQL Server の履歴テーブル対応です。

これらは最初の学習では少し地味に見えます。
しかし、業務システムを長く運用する上では非常に重要です。

同時実行制御とは

複数のユーザーが同じデータを編集する場合を考えます。

ユーザーAが車データを開く
ユーザーBも同じ車データを開く
ユーザーAが愛称を変更して保存する
ユーザーBが古い内容のまま色を変更して保存する

このとき、ユーザーAの変更が意図せず上書きされる可能性があります。

これを防ぐために、同時実行制御を使います。

RowVersion を使う

SQL Server では rowversion 列を使った楽観的同時実行制御がよく使われます。

モデルにバージョン列を追加します。

public sealed class Car
{
    public int Id { get; set; }
    public string Make { get; set; } = "";
    public string Color { get; set; } = "";
    public string PetName { get; set; } = "";

    public byte[] RowVersion { get; set; } = Array.Empty<byte>();
}

設定です。

modelBuilder.Entity<Car>()
    .Property(car => car.RowVersion)
    .IsRowVersion();

更新時に、EF Core は取得時の RowVersion を条件に含めます。
別のユーザーが先に更新していると、対象行が見つからず例外になります。

try
{
    await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
    Console.WriteLine("他のユーザーによってデータが更新されています。");
}

競合時の対応

競合が起きた場合の対応方針は、業務によって異なります。

  • ユーザーへ再読み込みを促す
  • データベースの値を優先する
  • 自分の変更を再適用する
  • 差分を表示して選ばせる

現在値、元の値、データベース値を取得できます。

catch (DbUpdateConcurrencyException ex)
{
    foreach (var entry in ex.Entries)
    {
        var databaseValues = await entry.GetDatabaseValuesAsync();

        if (databaseValues == null)
        {
            Console.WriteLine("対象データは削除されています。");
            continue;
        }

        Console.WriteLine("データベース上の値:");

        foreach (var property in entry.Metadata.GetProperties())
        {
            object? currentValue = entry.CurrentValues[property];
            object? databaseValue = databaseValues[property];

            Console.WriteLine($"{property.Name}: current={currentValue}, database={databaseValue}");
        }
    }
}

同時実行制御は、単に例外を捕まえるだけでなく、ユーザー体験としてどう扱うかが重要です。

接続回復

クラウド環境やネットワーク越しのデータベースでは、一時的な接続失敗が起こることがあります。

SQL Server プロバイダーでは、接続回復を有効にできます。

builder.Services.AddDbContext<AutoLotContext>(options =>
{
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("AutoLot"),
        sqlOptions =>
        {
            sqlOptions.EnableRetryOnFailure(
                maxRetryCount: 5,
                maxRetryDelay: TimeSpan.FromSeconds(10),
                errorNumbersToAdd: null);
        });
});

一時的なエラーに対して、自動的に再試行できます。

ただし、再試行してよい処理かどうかは考える必要があります。
外部 API 呼び出しや副作用のある処理と組み合わせると、意図せず二重実行になる可能性があります。

値変換

値変換を使うと、C# 側の型とデータベース側の保存形式を変換できます。

たとえば、列挙型を文字列として保存する例です。

public enum CarStatus
{
    Available,
    Sold,
    Maintenance
}
public sealed class Car
{
    public int Id { get; set; }
    public string Make { get; set; } = "";
    public CarStatus Status { get; set; }
}

設定です。

modelBuilder.Entity<Car>()
    .Property(car => car.Status)
    .HasConversion<string>();

データベースには "Available"、"Sold" のような文字列として保存されます。

数値として保存するより読みやすくなりますが、列挙値の名前変更には注意が必要です。

独自の値変換

値オブジェクトを保存形式へ変換することもできます。

public readonly record struct ProductCode(string Value);
public sealed class Product
{
    public int Id { get; set; }
    public ProductCode Code { get; set; }
}

設定です。

modelBuilder.Entity<Product>()
    .Property(product => product.Code)
    .HasConversion(
        code => code.Value,
        value => new ProductCode(value));

C# 側では ProductCode として扱い、データベースには文字列として保存できます。

値変換は便利ですが、複雑な変換を入れすぎるとクエリ変換や保守性に影響します。
単純で意味の明確な変換に使うのが良いです。

シャドウプロパティ

シャドウプロパティは、C# のクラスには存在しないが、EF Core モデル上には存在するプロパティです。

たとえば、全エンティティに作成日時を持たせたいが、ドメインクラスには出したくない場合です。

modelBuilder.Entity<Car>()
    .Property<DateTime>("CreatedAt")
    .HasDefaultValueSql("SYSUTCDATETIME()");

値を設定するには、Entry() からアクセスします。

Car car = new Car
{
    Make = "Honda",
    Color = "Blue",
    PetName = "Vega"
};

context.Cars.Add(car);

context.Entry(car).Property("CreatedAt").CurrentValue = DateTime.UtcNow;

await context.SaveChangesAsync();

問い合わせでも使えます。

List<Car> cars = await context.Cars
    .OrderBy(car => EF.Property<DateTime>(car, "CreatedAt"))
    .ToListAsync();

シャドウプロパティは、監査列や外部キー列で使われることがあります。
ただし、存在がコードから見えにくくなるため、チームでの共有が大切です。

SQL Server の履歴テーブル対応

SQL Server には、テーブルの変更履歴を保持する機能があります。
EF Core でも、履歴テーブルを扱う設定ができます。

設定例です。

modelBuilder.Entity<Car>()
    .ToTable("Inventory", tableBuilder =>
    {
        tableBuilder.IsTemporal();
    });

履歴テーブル名などを指定することもできます。

modelBuilder.Entity<Car>()
    .ToTable("Inventory", tableBuilder =>
    {
        tableBuilder.IsTemporal(temporalBuilder =>
        {
            temporalBuilder.UseHistoryTable("InventoryHistory");
            temporalBuilder.HasPeriodStart("ValidFrom");
            temporalBuilder.HasPeriodEnd("ValidTo");
        });
    });

過去時点のデータを問い合わせる例です。

DateTime pointInTime = DateTime.UtcNow.AddDays(-1);

List<Car> cars = await context.Cars
    .TemporalAsOf(pointInTime)
    .ToListAsync();

監査、履歴参照、誤更新の調査などで役立ちます。

実務機能を使うときの考え方

EF Core には多くの便利機能があります。
ただし、すべてを最初から使う必要はありません。

導入の目安です。

  • 論理削除が必要ならグローバルフィルター
  • 複数ユーザー編集があるなら同時実行制御
  • 一時的な接続失敗が想定されるなら接続回復
  • 値オブジェクトや列挙型を自然に扱いたいなら値変換
  • 監査列をモデルに出したくないならシャドウプロパティ
  • 変更履歴が必要なら履歴テーブル

機能を使う目的が明確だと、設計がぶれにくくなります。

まとめ

EF Core は、単に LINQ でデータベースを扱うための道具ではありません。
同時実行制御、接続回復、値変換、シャドウプロパティ、履歴テーブルなど、業務アプリケーションを支える機能も備えています。

大切なのは、機能を知った上で、必要な場面にだけ導入することです。
EF Core の抽象化を活かしつつ、データベースの制約や運用を意識できると、長く保守しやすいデータアクセス層を作れます。

C# .NET Entity Framework Core 同時実行制御 値変換 シャドウプロパティ 履歴テーブル
← [C#] EF Core の便利な問い合わせ機能を使う [C#] EF Core を中心にデータアクセス層を分ける →

Related Posts

  • [C#] EF Core でデータベースの準備と初期データを扱う Aug 8, 2026
  • [C#] EF Core とリポジトリでデータ操作を整理する Aug 7, 2026
  • [C#] DbContext の設定と保存処理を拡張する Aug 6, 2026
  • [C#] EF Core のエンティティと表示用モデルを設計する Aug 5, 2026

Table of Contents

  • 同時実行制御とは
  • RowVersion を使う
  • 競合時の対応
  • 接続回復
  • 値変換
  • 独自の値変換
  • シャドウプロパティ
  • SQL Server の履歴テーブル対応
  • 実務機能を使うときの考え方
  • まとめ

Recent Posts

  • [C#] EF Core でデータベースの準備と初期データを扱う Aug 8, 2026
  • [C#] EF Core とリポジトリでデータ操作を整理する Aug 7, 2026
  • [C#] DbContext の設定と保存処理を拡張する Aug 6, 2026
  • [C#] EF Core のエンティティと表示用モデルを設計する Aug 5, 2026
  • [C#] EF Core を中心にデータアクセス層を分ける Aug 4, 2026

Categories

  • C#150
  • .NET149
  • AWS27
  • Laravel16
  • Entity Framework Core15
  • Linux15
  • MySQL9
  • Apache8
  • PHP8
  • Data Access6
  • DynamoDB6
  • セキュリティ6
  • Nginx5
  • WordPress4
  • インフラ4
  • Hugo3
  • .NET Framework1
  • Aurora1
  • Diagnostics1
  • Filament1

Tags

  • C#
  • .NET
  • AWS
  • Laravel
  • コレクション
  • PHP
  • Entity Framework Core
  • セキュリティ
  • MySQL
  • Linux
  • パフォーマンス
  • Apache
  • LINQ
  • System.Collections.Generic
  • デリゲート
  • リフレクション
  • ADO.NET
  • Code Snippet
  • DynamoDB
  • NoSQL
  • PHP-FPM
  • RDS
  • System.Collections
  • Windows
  • メタデータ
  • メモリ管理
  • CIL
  • DoS
  • Nginx
  • SQL Server
  • WordPress
  • ラムダ式
  • 監視
  • 設計
  • Amazon Linux 2023
  • Delegate
  • Docker
  • IDisposable
  • Ipset
  • Iptables
  • LINQ to Objects
  • OPCache
  • Pointer
  • Reflection
  • System.Collections.Specialized
  • Unsafe
  • Webサーバー
  • アセンブリ
  • インターフェース
  • オブジェクト指向
Powered by Hugo & Explore Theme.