2020.11.21更新
本項にて説明しているよりも確実な方法があります。末尾を参照してください。
Entity Framework Core Code-Firstは楽ちんだけれど……
Dotnetでデータベースを扱うとき、EntityFrameworkは楽チンでいいですね。
特にCode Firstは先にデータベースのテーブル設計を行わなくても、テーブルに対応するPOCOとDbContextを実装しておけば、それに合わせてテーブルを作成してくれるから楽ちん。NoSQLなくても十分やっていけるレベル。
しかし、EntityFrameworkにはちょいと問題がありまして。いや、開発においては全く問題ないのですが、後々のことを考えると、ね。
カラムが思ったとおりに並んでくれない!
思っていたクラスと実際のテーブル
例えば、ユーザ情報を保持するUser
クラスがあったとします。User
クラスはエンティティ全てに共通するカラムを保持するEntityBase
クラスを継承します。
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FilunK.Sample.DataAccess.DataModel
{
public class EntityBase
{
[Column("CREATION_ID")]
[Required]
public string CreationId{get;set;}
[Column("CREATION_PG")]
[Required]
public string CreationProgram{get;set;}
[Column("CREATION_DATE")]
public DateTime CreationDate{get;set;}
[Column("UPDATE_ID")]
[Required]
public string UpdateId{get;set;}
[Column("UPDATE_ID")]
[Required]
public string UpdateProgram{get;set;}
[Column("UPDATE_DATE")]
[Required]
public DateTime UpdateDate{get;set;}
[Column("DEL_FLAG")]
[Required]
public bool IsDeleted{get;set;}
}
[Table("T_USER", Schema = "SAMPLE")]
public class User : EntityBase
{
[Key]
[Range(8, 100)]
[Column("USER_ID")]
public string UserId { get; set; }
[Required]
[Column("SALT")]
public string Salt { get; set; }
[Required]
[Column("HASH")]
public string Hash { get; set; }
[Required]
[Column("ITERATION")]
public int Iteration { get; set; }
[Required]
[Column("ACCOUNT_CONFIRMED")]
public bool IsConfirmed { get; set; }
}
}
このとき、期待しているテーブルの定義は以下のようなイメージでした。
※実際には文字列長を設定したりするので、厳密にこのSQLになるわけではありません。擬似的なSQLコードと見てもらえればと思います。
CREATE TABLE SAMPLE.USER {
USER_ID TEXT PRIMARY KEY
,SALT TEXT
,HASH TEXT
,ITERATION INTEGER
,ACCOUNT_CONFIRMED BOOLEAN
,CREATION_ID TEXT
,CREATION_PG TEXT
,CREATION_DATE TIMESTAMP
,UPDATE_ID TEXT
,UPDATE_PG TEXT
,UPDATE_DATE TIMESTAMP
,DEL_FLAG BOOLEAN
}
ですが、実際には以下のようなSQLのイメージになってしまいます。
CREATE TABLE SAMPLE.USER {
USER_ID TEXT PRIMARY KEY
,CREATION_DATE TIMESTAMP
,CREATION_ID TEXT
,CREATION_PG TEXT
,HASH TEXT
,ACCOUNT_CONFIRMED BOOLEAN
,DEL_FLAG BOOLEAN
,ITERATION INTEGER
,SALT TEXT
,UPDATE_ID TEXT
,UPDATE_PG TEXT
,UPDATE_DATE TIMESTAMP
}
ええ、カラムがの並びがぐちゃぐちゃですね。
プログラム視点では、DbContext経由でアクセスするのでクラス定義の通りに操作できるのであまり気になりません。
ですが、データベースの保守をしようとしたときにDBクライアント(DBeaverやA5SQL Mk2など)で見たときにはぐちゃぐちゃでたまったものではないですね。
Code First で生成されるテーブルのカラムを整える方法
どうやったらいい感じになるのだろう、と、[Column("COLUMN_NAME", Order = 1)]
といったふうなことも試してみましたが、最も確実なのは以下の方法でした。
※ColumnAttibuteクラスのOrderプロパティは、キーが複数ある場合の優先順位を指定するもので、Code-FirstでDBを生成する際のカラム順を設定するものではありません。
- Migrationコードの変更
EntityFrameworkCoreでは、dotnet ef
コマンドでデータベース作成コードの作成・実際のデータベース作成ができます。(.Net Framework EntityFrameworkではどうやるか分かっていないです)。
# このコマンドでデータベース作成コードの作成
dotnet ef migrations add <<Migration名>>
このコマンドで、Migrationsディレクトリ配下に以下のファイルができます。
ファイル名 | 説明 |
---|---|
<\<タイムスタンプ>>\_<\>.cs | Migration名におけるテーブル定義ソース? |
<\<タイムスタンプ>>\_<\>_Designer.cs | Migration名におけるカラム定義ソース? |
<\>ModelSnapshot.cs | DbContextのカラム定義ソース? |
それぞれがなんの役割なのかはよく分かっていません。詳しい人教えて!
ですが、この中の<<タイムスタンプ>>\_<<Migration名>>.cs
のUp
メソッドの中に注目です。
この中で記述されている、migrationBuilder.CreateTable
メソッドの引数にcolumns
がまさにテーブルのカラム順に影響しています。
ここの順序がちぐはぐな状態になっているため、最終的なデータベースのカラムもちぐはぐになります。
ここの並びを変更することで、期待したカラムの並びに変更することができました。
もっと確実な方法
DbContext::OnConfiguringでテーブル構成を実装する
注脚ベースでの実装の場合、カラムの並びはEntityFrameworkCore任せになってしまいますが、DbContext::OnConfiguringの場合、実装順にテーブル項目が決定される模様。
これから動的に更新されるMigrationファイルをいじる必要もなく期待したとおりのカラム順となります。
- とにかく簡単に! => 注脚ベース
- かゆいところもいじりたい => OnConfiguringベース
というところでしょうか。