前提・目的
- EF Coreを使った新規登録を行う。
- SQL Serverを利用。
- こんなテーブル
CREATE TABLE [dbo].[MyTableSample1](
[Id] [int] IDENTITY(1,1) NOT NULL,
[SubId] [nvarchar](10) NOT NULL,
[Name] [nvarchar](50) NULL
)
Id以外にSubIdというvarchar(10)の列があり、新規登録時にはシーケンスを参照して自動採番をしたい。
手順
SQL Server側の設定
シーケンスの生成
CREATE SEQUENCE [dbo].[MyTable1SubId1_SEQ]
AS [int]
START WITH 1
INCREMENT BY 1
シーケンス名は適当に。
SubIdにDEFAULT制約を指定し、シーケンスを参照させる
ALTER TABLE [dbo].[MyTableSample1] ADD CONSTRAINT [DF_MyTableSample1_SubId]
DEFAULT (NEXT VALUE FOR [MyTable1SubId1_SEQ]) FOR [SubId]
これによって、INSERT時にSubIdがnullなら自動的にシーケンスのNEXT VALUEを取ってきて設定してくれる。
EF Core側の設定
DbContextのOnModelCreatingに以下のように追記
HasDefaultValueSql()をSubIdプロパティに対して設定する。
modelBuilder.Entity<MyTableSample1>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK_MyTableSample1");
entity.ToTable("MyTableSample1");
entity.Property(e => e.SubId)
.IsRequired()
+ .HasDefaultValueSql();
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(50);
});
HasDefaultValueSql()を付けることでこのフィールドにDEFAULT制約があることを明示する。これによってEF CoreによるINSERT時にこのフィールドがnullであっても例外を上げなくなる。
このHasDefaultValueSql()は、そのまま空の引数でもよいが、できれば実際のDEFAULT制約の指定値を設定しておく方が良い。
.HasDefaultValueSql("(NEXT VALUE FOR [MyTable1SubId1_SEQ])");
こうすることで、Code FirstによるDB生成時に自動的にDEFAULT制約を付与してくれる。
先にDB側でこのDEFAULT制約を設定してからDbContextをスキャッフォルドしなおせば、自動的に上記のコードが付加されるものと思われる。
勘違いしやすいが、上記のようにC#側で HasDefaultValueSql("(NEXT VALUE FOR シーケンス名)") を指定すれば、INSERT時に自動的に規定値が設定されるわけではない。この点は最初勘違いしてドはまりした。ネット上にも勘違いしていると思われる記事が見られ、Copilotも嘘情報を教えてくる。この引数にはあくまでもCode First時の参照情報としての役割しかないようで、例えば"test"のような適当な値を設定してもINSERT時にh何もエラーは起きない。
新規登録コードのサンプル
var entitiy = new MyTableSample1
{
Name = "テスト",
};
db.Add(entitiy);
await db.SaveChangesAsync();
// SaveChanges後、EF Coreによってentity.Id と entity.SubId には新しい値が設定される
上記のコメントにもあるように、SaveChanges()の後、entity.Idとentity.SubIdには自動的に採番された値が設定される。
これはEF Core内部ではSQLのOUTPUT句を用いて実現されている。
さすがEF Core、便利なことこの上ない。
SET NOCOUNT ON;
INSERT INTO [MyTableSample1] ([Name])
OUTPUT INSERTED.[Id], INSERTED.[SubId] -- この行によってIdとSubIdが返される
VALUES (@p0);
あとがき
SubIdの型がvarchar(10)なのはわざとで、intにしない場合どうなるのかなと思ったが問題なく文字列として数値が設定された。
記事中にも書いたが、最初HasDefaultValueSqlを指定することでINSERT時にデフォルト値が適用されるようになるよ、というような事をCopilotが言い出して、ホンマかと思ってやってみたら普通に「null非許容フィールドにnullを設定しようとしました」みたいなエラーが出て「なんでや!?」と思って公式サイトを見に行くも、確かにそのような話が書かれており…。
しかしここで「これひょっとしてCode First前提なのでは」と思い当たり、そこから調べた結果、どうやらそうらしいということで、同じ事でハマってる人の救いになればと記事に残しておきます。
参考