🌊

EF CoreのScaffoldingによる想定外の挙動:NotNullカラムとデフォルト値の問題

2024/09/27に公開

まとめ

  • テーブルのカラムがNotNullかつDefault値が設定されているとEFCoreでは想定外の挙動となり得る。
  • Scaffoldingで自動生成すると発生しやすい。
  • モデル化した際にNull値が入らないにもかかわらずデフォルト値が設定されてしまうので、DB設計かモデル化の内容を修正する。

前提

  • EF Core 8 (8.0.8)
  • postgresql 16.3

以下のようなテーブルを考えます。

create table test_table (
  id text not null
  , status_code integer default 1 not null -- デフォルトは1としたいけど0もOK。
);

本題

dotnet scaffoldingでコードの生成を行うと以下のようなものが作られます。

test_table.cs
[Table("test_table")]
public partial class TestTable
{
    [Key]
    [Column("id")]
    public string Id { get; set; } = null!;
    [Column("status_code")]
    public int StatusCode { get; set; }
}
dbcontext.cs
//抜粋
    modelBuilder.Entity<TestTable>(entity =>
    {
        entity.Property(e => e.RequirementStatusCode)
            .HasDefaultValue(1)
    });

このとき、DbContext.Addで以下のように追加するとStatusCodeに1が設定されます。

var これ = TestTable(){Id=1,statusCode=0}; //0を指定しているのに...
await DbContext.AddAsync(これ);
await DBContext.SaveChangesAsync(); //実際にはStatusCodeに1が挿入される(!?)

原因&対応

デフォルト値(intの場合は0)が渡されたときは値が渡されていない場合と同じ挙動となるっぽい。
そりゃそうだと思いますが、Nullableの型に変換してくれてもいいのに...。
https://learn.microsoft.com/ja-jp/ef/core/change-tracking/miscellaneous#working-with-default-values

なので、対応としてはこんな感じ。

  1. 型をintint?にする。
test_table.cs
[Table("test_table")]
public partial class TestTable
{
    [Key]
    [Column("id")]
    public string Id { get; set; } = null!;
    [Column("status_code")]
    public int? StatusCode { get; set; } //Nullableに。
}

ただ現状Scaffoldingでやってるので、毎回直すのは手間。

  1. DB設計を考え直す。
    元々のカラムをNullableにすれば問題解決。Defaultを0にするような設計にするのが一番いいのだろうけど...。

終わり。

Discussion