EntityFrameworkの外部キー関係のエラー(C#)

C#

nullableではない値の外部キーを設定した場合に以下のエラーが発生しました。
エラーの中身としてはNullableかどうかは本質的ではなさそうですが、とりあえずの解決方法をメモしておきます。

may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints

クラスの定義はこんな感じ。

public class Book{
    public int Id { get; set; }

    public int BookId { get; set; }

    public Book Book { get; set; } = null!;
}
public class Author{
    public int Id { get; set; }

    public string Name{ get; set; }

    public List<Book> Books { get; set; } = null!;
}

1対多の構造をしていています。実際に上記のエラーが発生した実Book/Authorクラスは他にもForignKeyも持っています。

とりあえずの解決方法はこちら。
OnDeleteの時にどうしてあげたいかを指定すればよいです。

builder.Entity<Book>()
    .HasOne(w => w.Author)
    .WithMany(b => b.Books)
    .OnDelete(DeleteBehavior.Restrict);

外部キーの参照先が消されたら、参照してる側のentityをどうするのか?ってのを決める必要があります。
デフォルトの挙動はsetNullならしく、参照が消えたらnullが代入されるようです。
nullableじゃない値を指定している場合(int?じゃなくてint)、setNullできませんのでCascadeが自動的に指定され、参照してる側のentityも一緒に削除されるようになります。

そして、話がややこしいのはDeleteBehavior.Restrictの場合で、こちらは指定していてもSetNullと同等の効果が発動します。参照してる側のentityがあるから参照先のEntityは消せませんよーって例外を出すわけではなく、デフォルト動作に乗っとってSetNullを試みます。が、ここで対象のプロパティがNullableじゃない場合、NOT NULL制約があるためnull代入できずにエラーを吐くことになります。結果として参照先も消せません。

一方で、Nullableなカラムに対してDeleteBehavior.Restrictを使った場合、不幸にしてNullが代入できるためエラーも吐かずに参照先Entityを削除できると思われます。

今回私が取った解決方法はEntityを消して欲しくなかったため
参照プロパティをNullableでなくした上でOnDeleteにRestrictを指定しました。

Entityが消えればCascadeせずNull代入が希望する動作であれば、
参照プロパティをNullableにすれば解決すると思います。

DeleteBehavior 列挙型 (Microsoft.EntityFrameworkCore)
プリンシパルが削除されたとき、またはリレーションシップが切断されたときに、リレーションシップ内の依存エンティティに削除操作を適用する方法を示します。

この辺りの記事も参考になると思います。
‘SQL Server では ON DELETE RESTRICT がサポートされておらず‘
などと不穏なことが書いてあるのでビビりますが、結局SetNullされるよーってだけのようです。

リレーショナル データベースでの ON DELETE NO ACTION (データベースの既定値) と ON DELETE RESTRICT の動作は、通常は同一であるか非常に似ています。NO ACTION が示唆することとは異なり、これらの両方のオプションによって参照に関する制約が適用されます。 相違点は、データベースで “いつ” 制約がチェックされるかです。 使用しているデータベース システムでの ON DELETE NO ACTION と ON DELETE RESTRICT の具体的な違いについては、そのデータベースのドキュメントを参照してください。

SQL Server では ON DELETE RESTRICT がサポートされておらず、代わりに ON DELETE NO ACTION が使用されます。

データベースでカスケード動作を発生させる値は、Cascade と SetNull だけです。 その他はすべて、どの変更もカスケードさせないようにデータベースを構成する値です。

連鎖削除 - EF Core
エンティティがプリンシパルまたは親から切断されたときにトリガーされるカスケード動作の構成

コメント