前回は、データベースの準備と初期データ投入を扱いました。
今回は、すでに存在するデータベースから EF Core のモデルを作る流れを見ます。
新規開発なら、C# のエンティティを書き、マイグレーションでデータベースを作る形がわかりやすいです。
しかし実務では、すでに動いているデータベースを .NET アプリケーションから使いたい場面も多くあります。
その場合は、既存のテーブルやビューから DbContext とエンティティを生成して、そこから整えていくのが現実的です。
既存データベースから生成する場面
既存データベースからモデルを生成する場面には、たとえば次のようなものがあります。
- 古いアプリケーションを .NET へ移行する
- すでに運用中の業務データベースを新しい画面から参照する
- データベース設計が先に決まっている
- 他システムが所有するテーブルを読み取りたい
- 既存のビューやストアドプロシージャを活用したい
このような場合、最初から C# 側だけで理想のモデルを作ることはできません。
データベースの現実を受け止めた上で、アプリケーションに合う形へ少しずつ整えていきます。
生成コマンドの基本
既存データベースからモデルを生成するには、EF Core のコマンドを使います。
dotnet ef dbcontext scaffold \
"Server=(localdb)\mssqllocaldb;Database=AutoLot;Trusted_Connection=True;" \
Microsoft.EntityFrameworkCore.SqlServer \
--context AutoLotContext \
--context-dir Data \
--output-dir Entities
Windows のコマンドプロンプトでは、改行方法が少し違います。
1 行で書くと次のようになります。
dotnet ef dbcontext scaffold "Server=(localdb)\mssqllocaldb;Database=AutoLot;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer --context AutoLotContext --context-dir Data --output-dir Entities
このコマンドにより、テーブル構造に合わせて DbContext とエンティティが生成されます。
生成されたコードは完成品というより、出発点です。
命名、名前空間、不要な設定、部分クラス化などを確認しながら整えていきます。
接続文字列をコードへ埋め込まない
生成時に、接続文字列が OnConfiguring() に入ることがあります。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
"Server=(localdb)\\mssqllocaldb;Database=AutoLot;Trusted_Connection=True;");
}
学習用にはわかりやすいですが、実務では避けたい形です。
接続文字列は設定ファイルや環境変数から渡す方が安全です。
生成時に、接続文字列を埋め込まない指定を使えます。
dotnet ef dbcontext scaffold \
"Name=ConnectionStrings:AutoLot" \
Microsoft.EntityFrameworkCore.SqlServer \
--context AutoLotContext \
--context-dir Data \
--output-dir Entities \
--no-onconfiguring
アプリケーション側では、依存性注入で設定します。
builder.Services.AddDbContext<AutoLotContext>(options =>
{
options.UseSqlServer(
builder.Configuration.GetConnectionString("AutoLot"));
});
データアクセス層を保つ上で、接続情報をどこに置くかは重要です。
コードに固定すると、環境ごとの差し替えが難しくなります。
生成されたエンティティを読む
たとえば、既存の Inventory テーブルから次のようなエンティティが生成されたとします。
public partial class Inventory
{
public int Id { get; set; }
public string Make { get; set; } = null!;
public string Color { get; set; } = null!;
public string PetName { get; set; } = null!;
public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
}
生成コードでは、データベースのテーブル名に近いクラス名になることがあります。
アプリケーション上では Car と呼びたいなら、生成後に調整するか、生成時に名前を変える方針を決めます。
partial が付いている点も大事です。
生成されたファイルを直接編集すると、再生成時に上書きされる可能性があります。
追加したい処理は、別ファイルの部分クラスに分けると安全です。
public partial class Inventory
{
public string DisplayName => $"{Make} {Color} {PetName}";
}
ただし、表示専用の処理をエンティティへ増やしすぎると、保存の型と画面の型が混ざります。
本格的な画面では、表示用モデルへ投影する方が扱いやすいです。
ビューを読み取り専用モデルとして扱う
既存データベースには、集計や一覧表示用のビューがあることがあります。
CREATE VIEW dbo.CarOrderSummaries
AS
SELECT
c.Id AS CarId,
c.Make,
c.Color,
COUNT(o.Id) AS OrderCount
FROM dbo.Inventory AS c
LEFT JOIN dbo.Orders AS o ON c.Id = o.CarId
GROUP BY c.Id, c.Make, c.Color;
EF Core では、キーなしエンティティとしてビューを扱えます。
public sealed class CarOrderSummary
{
public int CarId { get; set; }
public string Make { get; set; } = "";
public string Color { get; set; } = "";
public int OrderCount { get; set; }
}
設定です。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CarOrderSummary>(entity =>
{
entity.HasNoKey();
entity.ToView("CarOrderSummaries");
});
}
DbSet も用意します。
public DbSet<CarOrderSummary> CarOrderSummaries => Set<CarOrderSummary>();
問い合わせは通常の DbSet と同じ感覚で書けます。
var summaries = await context.CarOrderSummaries
.Where(summary => summary.OrderCount > 0)
.OrderByDescending(summary => summary.OrderCount)
.ToListAsync();
ビューは読み取り専用の形として扱うのが基本です。
更新したい場合は、元のテーブルに対して処理を書く方が意図が明確です。
生成後にコード中心へ移る
既存データベースから始めた場合でも、途中から C# のモデルとマイグレーションを中心に管理したいことがあります。
その場合、最初に現在の状態を基準としてマイグレーションを作ります。
dotnet ef migrations add Baseline --ignore-changes
この指定により、現在のモデルをマイグレーション履歴の基準として記録できます。
すでに存在するテーブルを新規作成しようとしないため、既存データベースへ移行しやすくなります。
ただし、実際に使う前には生成されるマイグレーションと適用先をよく確認してください。
既存データベースを扱う作業は、データ損失につながる可能性があるためです。
生成コードを整える方針
生成したコードをそのまま使い続けるか、整えていくかはプロジェクトによります。
読み取り中心なら、生成コードを大きく変えない方が安全な場合があります。
一方で、アプリケーションの中心的なデータアクセスに使うなら、次のような整理が役立ちます。
- 名前空間をプロジェクト構成に合わせる
- テーブル名に引きずられたクラス名を見直す
- 表示用モデルと入力用モデルを分ける
- 接続文字列を設定ファイルへ移す
- マッピング設定を別クラスへ分ける
- 再生成に備えて独自処理は別ファイルへ分ける
既存データベースからの生成は、最終形ではありません。
現実のデータベースと C# の設計をつなぐための足場です。
この記事のまとめ
既存データベースから EF Core のモデルを生成すると、運用中のデータ資産を .NET アプリケーションで扱いやすくなります。
ただし、生成されたコードをそのまま信じすぎないことが大切です。
接続文字列、命名、ビューの扱い、マイグレーションへの移行方針を確認しながら、アプリケーションに合う形へ整えていきます。
次回は、データアクセス層で使う独自例外と、エンティティの仕上げ方を見ていきます。