bucket-sort logo bucket-sort

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

  • Posts
  • About
  • Contact
  1. Home
  2. All Posts
  3. [C#] EF Core を中心にデータアクセス層を分ける

[C#] EF Core を中心にデータアクセス層を分ける

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

前回までは、EF Core の追加、検索、更新、削除、同時実行制御などを個別に見てきました。

ここからは、もう少しアプリケーション全体の形に目を向けます。
EF Core は便利ですが、どこからでも DbContext を直接使えるようにしてしまうと、処理の置き場所が散らばりやすくなります。

小さなサンプルでは問題になりません。
しかし業務アプリケーションでは、画面、入力検証、ドメインのルール、データベース操作、例外処理、テストの準備が少しずつ絡み合います。

そこで、データベースに触れる処理を データアクセス層 として分けて考えます。

データアクセス層を分ける理由

たとえば画面側の処理に、次のようなコードが直接書かれているとします。

public async Task<IActionResult> Index()
{
    await using var context = new AutoLotContext(_options);

    var cars = await context.Cars
        .Where(car => car.IsDrivable)
        .OrderBy(car => car.Make)
        .ThenBy(car => car.PetName)
        .ToListAsync();

    return View(cars);
}

これは動きます。
ただ、同じような検索条件が別の画面、バッチ処理、API にも出てくると、次第に重複が増えます。

また、データベースの都合が画面側へ漏れていきます。

  • テーブル名や列名に近い考え方が画面側に出る
  • 関連データをどう読み込むかがあちこちに散る
  • 同時実行制御や削除制約の扱いが画面ごとにばらつく
  • テストでデータベースの準備が大きな負担になる

データアクセス層は、こうした散らばりを抑えるための境界です。

プロジェクトを分ける考え方

中規模以上のアプリケーションでは、次のようにプロジェクトを分けると見通しがよくなります。

AutoLot.Models
  エンティティ、列挙型、表示用モデル

AutoLot.Dal
  DbContext、マッピング設定、リポジトリ、マイグレーション

AutoLot.Web
  画面、API、入力、表示

AutoLot.Tests
  データアクセス層や業務処理のテスト

Models はできるだけデータ構造に集中します。
Dal はデータベースとのやり取りを受け持ちます。
Web は利用者との接点に集中します。

参照関係は、一方向にしておくと扱いやすくなります。

AutoLot.Web
  -> AutoLot.Dal
  -> AutoLot.Models

AutoLot.Tests
  -> AutoLot.Dal
  -> AutoLot.Models

Models が Web や Dal に依存しないことが大切です。
中心にあるモデルが外側の都合を知らない形にしておくと、利用場所が増えても壊れにくくなります。

モデル用プロジェクト

モデル用プロジェクトには、エンティティを置きます。

namespace AutoLot.Models.Entities;

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 bool IsDrivable { get; set; } = true;

    public List<Order> Orders { get; set; } = new();
}
namespace AutoLot.Models.Entities;

public sealed class Order
{
    public int Id { get; set; }
    public int CarId { get; set; }
    public string CustomerName { get; set; } = "";
    public DateTime OrderDate { get; set; } = DateTime.UtcNow;

    public Car? Car { get; set; }
}

モデルはシンプルに見えますが、アプリケーションの言葉を表す重要な場所です。
データベースの列を写すだけではなく、「このアプリケーションでは何を扱っているのか」を表現します。

データアクセス用プロジェクト

データアクセス用プロジェクトには、DbContext を置きます。

using AutoLot.Models.Entities;
using Microsoft.EntityFrameworkCore;

namespace AutoLot.Dal;

public sealed class AutoLotContext : DbContext
{
    public AutoLotContext(DbContextOptions<AutoLotContext> options)
        : base(options)
    {
    }

    public DbSet<Car> Cars => Set<Car>();
    public DbSet<Order> Orders => Set<Order>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Car>(entity =>
        {
            entity.ToTable("Inventory");
            entity.HasKey(car => car.Id);

            entity.Property(car => car.Make)
                .HasMaxLength(50)
                .IsRequired();

            entity.Property(car => car.Color)
                .HasMaxLength(50)
                .IsRequired();

            entity.Property(car => car.PetName)
                .HasMaxLength(50);
        });

        modelBuilder.Entity<Order>(entity =>
        {
            entity.ToTable("Orders");
            entity.HasKey(order => order.Id);

            entity.HasOne(order => order.Car)
                .WithMany(car => car.Orders)
                .HasForeignKey(order => order.CarId);
        });
    }
}

DbContext は、単なる接続クラスではありません。
エンティティの集合、変更追跡、マッピング、保存処理の中心です。

だからこそ、画面側の都合で肥大化させず、データアクセス層に閉じ込めておく価値があります。

設計時に使うファクトリ

マイグレーションを作るとき、EF Core のコマンドは DbContext を生成する必要があります。
実行時には依存性注入で作ればよいのですが、コマンド実行時には Web アプリケーション全体を起動したくない場合があります。

そのために、設計時用のファクトリを用意できます。

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;

namespace AutoLot.Dal;

public sealed class AutoLotContextFactory
    : IDesignTimeDbContextFactory<AutoLotContext>
{
    public AutoLotContext CreateDbContext(string[] args)
    {
        var options = new DbContextOptionsBuilder<AutoLotContext>()
            .UseSqlServer(
                "Server=(localdb)\\mssqllocaldb;Database=AutoLot;Trusted_Connection=True;")
            .Options;

        return new AutoLotContext(options);
    }
}

これで、データアクセス用プロジェクトに対してマイグレーションを作りやすくなります。

dotnet ef migrations add InitialCreate --project AutoLot.Dal --startup-project AutoLot.Web
dotnet ef database update --project AutoLot.Dal --startup-project AutoLot.Web

実務では接続文字列をコードに直書きしないことが多いです。
ただし、設計時ファクトリの役割を理解するための最小例としては、この形がわかりやすいでしょう。

グローバル using を整理する

プロジェクトを分けると、各ファイルの using が増えます。
よく使う名前空間は、GlobalUsings.cs にまとめられます。

global using AutoLot.Models.Entities;
global using Microsoft.EntityFrameworkCore;

便利ですが、増やしすぎると「どこから来た型なのか」が見えにくくなります。
本当に多くのファイルで使うものだけに絞るのが扱いやすいです。

例外も境界に置く

データアクセス層では、独自例外を用意しておくと、呼び出し側の処理が読みやすくなります。

namespace AutoLot.Dal.Exceptions;

public sealed class RecordNotFoundException : Exception
{
    public RecordNotFoundException(string message)
        : base(message)
    {
    }
}

たとえば、指定された車が存在しない場合に使えます。

public async Task<Car> FindCarAsync(int id)
{
    var car = await _context.Cars.FindAsync(id);

    return car ?? throw new RecordNotFoundException(
        $"車が見つかりません。Id: {id}");
}

DbUpdateException や SqlException をそのまま画面側に伝えると、データベースの都合が外へ漏れます。
必要に応じて、アプリケーションの言葉に変換する場所を用意しておくとよいです。

最初から層を増やしすぎない

データアクセス層を分けることは有効です。
しかし、小さなアプリケーションに対して最初から複雑な抽象化を入れすぎると、かえって読みづらくなります。

判断の目安は、次のあたりです。

  • 同じ検索条件が複数箇所に出てきた
  • 画面側に Include() や保存時の例外処理が増えてきた
  • テストでデータベース準備を共通化したくなった
  • 複数の画面や API から同じ更新処理を使いたくなった
  • 削除、履歴、監査などの共通ルールが必要になった

構造は、コードの重さを減らすためにあります。
見た目だけをきれいにするためではありません。

次回は、エンティティと表示用モデルをもう少し具体的に設計します。

C# .NET Entity Framework Core データアクセス層 アーキテクチャ
← [C#] EF Core の実務向け機能を理解する [C#] EF Core のエンティティと表示用モデルを設計する →

Related Posts

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

Table of Contents

  • データアクセス層を分ける理由
  • プロジェクトを分ける考え方
  • モデル用プロジェクト
  • データアクセス用プロジェクト
  • 設計時に使うファクトリ
  • グローバル using を整理する
  • 例外も境界に置く
  • 最初から層を増やしすぎない

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.