コードファーストでDB定義書を自動生成したい
開発者としてはコードファーストでDBを管理しているとき、「コード(モデル + マイグレーション)を唯一のマスタ」 にして運用したい
しかし、その現実は以下の問題が現場で発生する。
1.現場で起きた問題
1-1. コードが正なのに、説明資料(定義書)が求められる
実装・運用の正は migration で確定しているので、開発チーム内では困らない。
ただし外部共有・監査・レビューの局面では、いまだに 「テーブル定義書」 の提出が必要になることが多い。
- 取引先に提出する成果物として必要
- テスト観点レビューで一覧性 が欲しい
- 監査・セキュリティの資料として求められる
結果として、「コードはあるのに、別途ドキュメントを作らされる」状態になる。
1-2. 手書き定義書はすぐ陳腐化する(=二重管理になる)
一度 Excel/Google スプレッドシートで作り始めると、更新タイミングがズレる。
- 変更は migration に入ったが、定義書が更新されていない
- 逆に定義書は直したが、実装が追いついていない
- どっちが正? という議論が毎回発生する
結局、「正はコード」 と言いながら、現場では “定義書も正扱いされる” という矛盾が生まれる。
1-3. SQL(migration)そのままでは読めない人がいる
「migration の SQL/コードが仕様書です」で押し切れれば理想だが、現実はそうもいかない。
- SQLを読める人ばかりではない
- 制約・インデックス・FK が散らばっていて追いにくい
- 仕様書としてのフォーマット要件がある(会社のテンプレ等)
つまり、“誰でも読める形” に整形された成果物が必要になる。
1-4. レビューの観点が不足しやすい(暗黙の事故が起きる)
コードファーストだと、変更はコード差分としては分かるが、DBの観点では見落としが出やすい。
- nullable 変更(Not Null化)
- default の追加/変更
- index の追加漏れ
- FK/参照整合性の取り扱い
- 型の微妙な違い(nvarchar(100) → nvarchar(255) など)
「DBとしてどう変わったか」が一覧で見えないため、レビューで事故る。
1-5. “今のDBの姿” を共有する方法がない
コードは Git にある。migration もある。
しかし、最新版スキーマの全体像をパッと共有する仕組みがない。
- いちいち DB に接続して
sys.tablesを叩く - 開発環境がないと確認できない
- 結局、誰かがスクショや手作業でまとめる
これが積み重なると、「定義書を更新しないといけない」という話に戻ってしまう。
1-6. 解決方針:マスタ(DB)からドキュメントを生成にする
これらをまとめると、やりたいことは以下
- マスタはコード(モデル + migration)
- DBの最終状態(適用後)から
- 誰でも読める形式(HTML/PDF等)で
- 定期的に(CIで)自動生成する
2. DB(sys.*)からスキーマ情報を抽出する
前章で述べた通り、マスタは コード(モデル + migration) だが、ドキュメント生成は 「migration適用後のDB」 を基準にしたい。
そのために、SQL Server が持つシステムカタログ(sys.*)からスキーマ情報を取得する。
2-1. なぜ sys.* から取るのか
コードからもある程度は生成できるが、最終的に欲しいのは DBに適用された実体。
- 実際の型(nvarchar(255) など)
- Default 制約
- PK / Index / Unique
- FK(参照関係)
- 可能ならコメント(
MS_Description)
これらは DBの最終状態を見れば確実であり、EFの実装や条件付き設定が混ざっても 今のDB を正として出せる。
2-2. 抽出する情報(最小セット)
今回は「テーブル定義書」を作るために、以下を抽出する。
- テーブル一覧:schema / table / description
- カラム一覧:column / type / nullable / default / PK / description
- インデックス:index name / unique / pk / columns(includeは任意)
- 外部キー(リレーション):親子関係 / FK名 / ON DELETE / ON UPDATE
2-3. テーブル一覧を取得するSQL
sys.tables と sys.schemas をJOINして一覧を作る。
コメントは sys.extended_properties の MS_Description を使う(任意)。
SELECT
s.name AS schema_name,
t.name AS table_name,
CAST(ep.value AS nvarchar(max)) AS description
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
LEFT JOIN sys.extended_properties ep
ON ep.major_id = t.object_id
AND ep.minor_id = 0
AND ep.name = 'MS_Description'
ORDER BY s.name, t.name;
2-4. カラム一覧を取得するSQL(型・NULL・DEFAULT・PK・コメント)
PKは sys.indexes.is_primary_key = 1 の sys.index_columns から列を拾う。
;WITH pk_cols AS (
SELECT ic.object_id, ic.column_id
FROM sys.indexes i
JOIN sys.index_columns ic
ON ic.object_id = i.object_id AND ic.index_id = i.index_id
WHERE i.is_primary_key = 1
)
SELECT
s.name AS schema_name,
t.name AS table_name,
c.column_id,
c.name AS column_name,
ty.name AS type_name,
c.max_length,
c.precision,
c.scale,
c.is_nullable,
dc.definition AS default_definition,
CASE WHEN pk.column_id IS NULL THEN 0 ELSE 1 END AS is_pk,
CAST(ep.value AS nvarchar(max)) AS description
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
JOIN sys.types ty ON ty.user_type_id = c.user_type_id
LEFT JOIN sys.default_constraints dc
ON dc.parent_object_id = t.object_id
AND dc.parent_column_id = c.column_id
LEFT JOIN pk_cols pk
ON pk.object_id = t.object_id
AND pk.column_id = c.column_id
LEFT JOIN sys.extended_properties ep
ON ep.major_id = t.object_id
AND ep.minor_id = c.column_id
AND ep.name = 'MS_Description'
ORDER BY s.name, t.name, c.column_id;
2-5. インデックス一覧を取得するSQL(キー列 / include列)
インデックスは sys.indexes と sys.index_columns を軸に組む。
is_included_column で include列が分かる。
SELECT
s.name AS schema_name,
t.name AS table_name,
i.name AS index_name,
i.type_desc,
i.is_unique,
i.is_primary_key,
ic.key_ordinal,
c.name AS column_name,
ic.is_descending_key,
ic.is_included_column
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.indexes i ON i.object_id = t.object_id
JOIN sys.index_columns ic
ON ic.object_id = i.object_id AND ic.index_id = i.index_id
JOIN sys.columns c
ON c.object_id = t.object_id AND c.column_id = ic.column_id
WHERE i.type_desc <> 'HEAP'
ORDER BY s.name, t.name, i.name, ic.is_included_column, ic.key_ordinal;
2-6. 外部キー(FK)一覧を取得するSQL
FKは sys.foreign_keys と sys.foreign_key_columns で取得できる。
ON DELETE / ON UPDATE も *_referential_action_desc で取れる。
SELECT
ps.name AS parent_schema,
pt.name AS parent_table,
pc.name AS parent_column,
rs.name AS ref_schema,
rt.name AS ref_table,
rc.name AS ref_column,
fk.name AS fk_name,
fk.delete_referential_action_desc AS on_delete,
fk.update_referential_action_desc AS on_update
FROM sys.foreign_keys fk
JOIN sys.foreign_key_columns fkc
ON fkc.constraint_object_id = fk.object_id
JOIN sys.tables pt
ON pt.object_id = fk.parent_object_id
JOIN sys.schemas ps
ON ps.schema_id = pt.schema_id
JOIN sys.columns pc
ON pc.object_id = pt.object_id AND pc.column_id = fkc.parent_column_id
JOIN sys.tables rt
ON rt.object_id = fk.referenced_object_id
JOIN sys.schemas rs
ON rs.schema_id = rt.schema_id
JOIN sys.columns rc
ON rc.object_id = rt.object_id AND rc.column_id = fkc.referenced_column_id
ORDER BY ps.name, pt.name, fk.name, fkc.constraint_column_id;
2-7. 取得した情報を “定義書モデル” に組み立てる
抽出結果は「行」なので、そのままHTMLに出すよりも一度 中間モデルにするのが安定する。
ざっくりこんな形
-
DatabaseSpec
-
Title
-
Tables: List<TableSpec>
-
-
TableSpec
-
Schema, Name, Description
-
Columns: List<ColumnSpec>
-
Indexes: List<IndexSpec>
-
ForeignKeys: List<ForeignKeySpec>
-
このモデルを作ってしまえば、出力先は
-
Markdown
-
HTML
-
PDF
-
DBML(dbdiagram.io)
など、様々なフォーマットに変換できる
3. 出力
前章で取得した情報(tables / columns / indexes / fks)を、用途に応じて Markdown と HTML に整形して出力してみた
3-1. マークダウンで出力

利点
-
レビューしやすい
PRで差分がそのまま見える。
「カラム追加」「型変更」「NOT NULL化」などが、文章ではなく差分として追える。 -
リンクで辿れる(軽いドキュメントポータルになる)
index.md(テーブル一覧)→tables/{schema}.{table}.md(詳細)という構造にすると、GitHub上で迷子にならない。 -
実装と同じリポジトリで完結する
“定義書だけ別の場所にある” 問題が消える。
仕様書の更新漏れを減らせる。
3-2. HTMLで出力

利点
-
日本の現場で求められる“定義書フォーマット”を再現しやすい
見出しの色、罫線、A4レイアウト、ページ区切り、ヘッダ/フッタ(作成日・版数)など。
“いかにも提出物” な形に寄せられる。 -
PDF化が簡単(HTML→PDF)
Playwright等でA4 PDFに変換できる。
外部提出・監査対応でそのまま使える。 -
成果物として扱いやすい
ZIPで渡せる/Pagesに置ける/社内ポータルに配置できる。
4. まとめ
コードファーストでDBを管理する開発チームが抱える根本課題は、
「コードが唯一の真実なのに、他形式の成果物が求められる」という矛盾にあります。
| # | 課題 | 背景 |
|---|---|---|
| 1 | コードが正でも「定義書」が求められる | 外部提出・監査対応で成果物が必要 |
| 2 | 手書き定義書がすぐ陳腐化する | 二重管理が避けられない |
| 3 | migration/SQLが読めない人がいる | 関係者の非エンジニア比率が高い |
| 4 | レビューでDB差分が見えにくい | 「nullable変更」「FK変更」などが埋もれる |
| 5 | 「今のDB状態」を共有しづらい | 実環境に接続しないと見えない |
解決方針:
「コード(モデル+マイグレーション)」を唯一のマスタにし、
そこから“誰でも読めるドキュメント”を自動生成する。
これにより、次のような理想状態を実現できる。
| 項目 | Before(従来) | After(自動生成) |
|---|---|---|
| 定義書作成 | 手作業でExcel | CIで自動生成(Markdown/HTML) |
| 更新漏れ | 頻発 | migration適用後に常に最新化 |
| レビュー | コード差分+人の記憶 | Markdown差分でDB構造変化を可視化 |
| 外部共有 | 別作業で資料化 | HTML/PDF成果物を自動出力 |
| 信頼の基準 | Excel or DB or コード | コード+migrationが唯一の正 |
5. 最後に
抽出したスキーマ情報を具体的にどうマークダウンやHtmlに落とし込んだかの具体的な処理はそのうち書くかもです。
Discussion