【C#】Entity Framework Coreでテーブルに行データ(レコード)を追加する方法

目次

概要

Entity Framework Core (EF Core) を使用して、データベースのテーブルに新しい行(レコード)を挿入する基本的な手順を解説します。 単一のエンティティを追加する方法に加え、リレーション(関連)を持つ複数のテーブルに対して、オブジェクト参照または外部キーIDを使用してデータを登録する手法も紹介します。

仕様(入出力)

  • 入力: 新規作成したエンティティクラスのインスタンス。
  • 処理: DbSet<T>.Add() メソッドで追跡対象に追加し、SaveChangesAsync() でデータベースに反映(COMMIT)する。
  • 出力: データベースにレコードが作成され、自動採番された主キー(ID)がエンティティに反映される。

基本の使い方

DbContext のプロパティである DbSet に対して Add メソッドを呼び出し、最後に SaveChangesAsync を実行することで INSERT 文が発行されます。

// 1. インスタンス作成
var newServer = new Server { HostName = "Web-01" };

// 2. コンテキストへの追加(この時点ではDB未反映)
context.Servers.Add(newServer);

// 3. 保存(ここでINSERT文が実行される)
await context.SaveChangesAsync();

コード全文

以下のコードは、サーバー監視システムを想定したコンソールアプリケーションです。 「サーバー」マスタを作成した後、そのサーバーに関連付く「アクセスログ」を追加する一連の流れを実装しています。

※ 実行には Microsoft.EntityFrameworkCore.InMemory パッケージが必要です。 dotnet add package Microsoft.EntityFrameworkCore.InMemory

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace InsertDataSample
{
    // 1. エンティティクラスの定義
    public class Server
    {
        public int ServerId { get; set; }
        public string HostName { get; set; } = string.Empty;
        public string IpAddress { get; set; } = string.Empty;

        // リレーション:1つのサーバーは複数のログを持つ
        public ICollection<AccessLog> AccessLogs { get; set; } = new List<AccessLog>();
    }

    public class AccessLog
    {
        public int LogId { get; set; }
        public string RequestPath { get; set; } = string.Empty;
        public DateTime AccessedAt { get; set; }

        // 外部キー
        public int ServerId { get; set; }
        // ナビゲーションプロパティ
        public Server Server { get; set; }
    }

    // 2. DbContextの定義
    public class MonitoringContext : DbContext
    {
        public DbSet<Server> Servers { get; set; }
        public DbSet<AccessLog> AccessLogs { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            // 動作確認用にインメモリデータベースを使用
            optionsBuilder.UseInMemoryDatabase("MonitoringDb");
        }
    }

    class Program
    {
        static async Task Main(string[] args)
        {
            using (var context = new MonitoringContext())
            {
                // --- 親データの作成 ---
                var server1 = new Server
                {
                    HostName = "App-Server-01",
                    IpAddress = "192.168.1.10"
                };

                var server2 = new Server
                {
                    HostName = "DB-Server-01",
                    IpAddress = "192.168.1.20"
                };

                // DbSetに追加
                context.Servers.Add(server1);
                context.Servers.Add(server2);

                // データベースへ保存
                // SaveChangesが呼ばれるまでIDは未確定(0)です
                await context.SaveChangesAsync();

                Console.WriteLine($"Server1 Saved. ID: {server1.ServerId}");
                Console.WriteLine($"Server2 Saved. ID: {server2.ServerId}");


                // --- 子データの作成(関連データの追加) ---
                
                // パターンA: オブジェクト参照を使用する方法
                // 親エンティティ(server1)を直接代入します
                var log1 = new AccessLog
                {
                    Server = server1, 
                    RequestPath = "/api/v1/health",
                    AccessedAt = DateTime.Now
                };
                context.AccessLogs.Add(log1);

                // パターンB: 外部キーIDを使用する方法
                // 親エンティティのID(server2.ServerId)が分かっている場合に使用します
                var log2 = new AccessLog
                {
                    ServerId = server2.ServerId,
                    RequestPath = "/query/execute",
                    AccessedAt = DateTime.Now
                };
                context.AccessLogs.Add(log2);

                // まとめて保存
                await context.SaveChangesAsync();

                Console.WriteLine("ログデータを保存しました。");
                Console.WriteLine($"Log1 ID: {log1.LogId} (Server: {log1.Server.HostName})");
                Console.WriteLine($"Log2 ID: {log2.LogId} (ServerId: {log2.ServerId})");
            }
        }
    }
}

実行結果例

Server1 Saved. ID: 1
Server2 Saved. ID: 2
ログデータを保存しました。
Log1 ID: 1 (Server: App-Server-01)
Log2 ID: 2 (ServerId: 2)

カスタムポイント

  • AddRangeによる一括追加 複数のエンティティを一度に追加する場合は、Add を繰り返すよりも AddRange を使用する方がコードが簡潔になります。C#context.Servers.AddRange(server1, server2); // またはリストを渡す // context.Servers.AddRange(new List<Server> { s1, s2 });
  • 同期メソッドの利用 コンソールアプリやバッチ処理など、非同期である必要がない場合は SaveChanges()(同期版)を使用することも可能です。Webアプリではスケーラビリティの観点から SaveChangesAsync() が推奨されます。

注意点

  1. ID生成のタイミング ServerId などの自動採番される主キーは、Add した時点では「0」のままです。SaveChangesAsync() が完了し、データベースから戻り値を受け取った時点でエンティティのプロパティに値がセットされます。
  2. トランザクション SaveChangesAsync() は、呼び出された時点で保留中のすべての変更(Add, Update, Delete)を1つのトランザクションとして実行します。途中でエラーが発生した場合、すべての変更がロールバックされ、データの整合性が保たれます。
  3. インスタンスの再利用 一度 Add して保存したエンティティインスタンスを、別のコンテキストインスタンスで使い回す際は注意が必要です(追跡状態の管理が必要になるため)。基本的にはコンテキストごとにデータを取得し直すか、新しいインスタンスを作成します。

バリエーション(任意)

ナビゲーションプロパティ経由での追加

親エンティティのコレクションに子エンティティを追加するだけでも、EF Coreは関係性を認識し、保存時に自動的にINSERTしてくれます。

var server = new Server { HostName = "File-Server" };
// コレクションに直接Add
server.AccessLogs.Add(new AccessLog 
{ 
    RequestPath = "/files/download", 
    AccessedAt = DateTime.Now 
});

context.Servers.Add(server);
// 親(Server)と子(AccessLog)がまとめて保存されます
await context.SaveChangesAsync();

まとめ

SaveChangesAsync はトランザクションとして機能し、成功時には自動採番されたIDがオブジェクトに反映されます。

データの追加は DbSet.Add()SaveChangesAsync() のセットで行います。

関連データ(子レコード)の追加には、親オブジェクトをセットする方法と、親IDをセットする方法のどちらも利用可能です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次