EFCore の "所有されているエンティティ型"
.NET プログラミングにおけるデータベースアクセスライブラリの定番である、EntityFrame Core (以下、EFCore)。
EFCore を用いたデータベースを読み書きするプログラム開発において用いられる技法のひとつとして、"Code First" と呼ばれる作り方がある。
すなわち、EFCore を基盤とした C# ソースコードとしてデータベースのテーブル構造を記述してプログラムをビルドし、そのプログラムの実行によって、データベースとその構造を自動生成するというものだ。
自分は趣味/仕事の双方で、この "Code First" スタイルのプログラム開発を行なっている。
さてそのような "Code First" スタイルでの EFCore プログラミングにおいて、"所有されているエンティティ型" という機能が EFCore に用意されている。
これは何か、ちょっとサンプルコードを示してみよう。
まず、データベース上で People テーブルにマップすることにする、Person クラスというのを C# ソースコードとして記述する。
public class Person {
public int Id { get; set; }
[Required]
public string Name {get; set; }
public double Height { get; set; }
public double Weight { get; set; }
}
ところで、このプログラム開発において、Height と Weight の 2つのプロパティのペアが、この Person クラスに限らず他の多数のクラス (=テーブル) でも頻出するとしよう。
その場合、Height と Weight の 2つのプロパティを持つ独立したクラスを "所有されているエンティティ型" として作成し (ここでは Metric クラスとする) 各所で再利用できるようになる。
public class Person {
public int Id { get; set; }
[Required]
public string Name {get; set; }
public Metric Metric { get; set; }
}
[Owned] // <-- "所有されているエンティティ型" であることを記す
public class Metric {
public double Height { get; set; }
public double Weight { get; set; }
}
こうして実装したプログラムを実行し、データベースを自動生成されると、次のような People テーブルを作成してくれるのである。
![d0079457_19544646.png]()
"所有されているエンティティ型" を使った実装について、このサンプルコードだと今ひとつ効能がわかりにくいかもしれない。
しかし、同じセンサーデータ構成をセンサーの数だけ同一レコード上に列として並べたい場合など、効率良く且つミスなく実装できるので便利なのである。
なお、"所有されているエンティティ型" は他にもいくつかその自動生成されるデータベース構造にバリエーションがあるのだが、自分はもっぱら上記サンプルコードで示したようなパターンで利用している。
その他、"所有されているエンティティ型" について詳しくは、公式ドキュメントを参照されたし。
EFCore の v2.x と v.3.0 とで生成されるテーブル構造が異なる!
...と、まぁ、このような "所有されているエンティティ型" を活用したプログラム開発を、EFCore の ver.2.1~2.2 ベースで行なってきた。
そしてあくる日。
EFCore の次バージョン、ver.3.0 がリリースされたので、手持ちのプロジェクトの EFCore のバージョンを、ver.2.x から ver.3.0 に更新した。
そして動作を確認していたところ、"所有されているエンティティ型" に関して、自動生成されるテーブル構造が変わっていることに気がついた。
まずおさらいだが、EFCore v2.x においては、最初に示したサンプルコードだと、Height および Weight は、C# コード上は double 型なので、自動生成されるデータベース上の型は float NOT NULL というように非 null 許容となっている。
(以下にデータベース構造の図を再掲する)
![d0079457_19544646.png]()
これは少なくとも自分にとっては直感と相違ない振る舞いである。
もしデータベース上の型を null 許容としたければ、C# コード側を double? と記述することになる。
ところが、である。
EFCore v3.0 だと、同じコードからデータベースを自動生成するようにすると、なんと、C# コード上、"所有されているエンティティ型" に含まれる double 型が、データベース上では float NULL というように null 許容になってしまったのだ(下図)。
![d0079457_19544610.png]()
これが、"所有されているエンティティ型" に含まれるプロパティではなく、テーブルにマップされるクラス直属のプロパティであれば、これまでと変わらない振る舞いとなる。
すなわち、C# コードの double 型はデータベース上の float NOT NULL に、double? 型は float NULL となるようにデータベース構造が生成される。
この "所有されているエンティティ型" に含まれるプロパティが null 許容になってしまう振る舞いは、現時点での最新バージョン (但し安定版ではない) である v.3.1 Preview 3 や、安定版の最新パッチバージョン v.3.0.1 でも同じであった。
破壊的変更があるのは仕方がないが...
まぁ、EFCore も v.2 から v3 へとメジャーバージョンがあがるくらいなので、破壊的変更があるのは仕方がないとは思う。
また、もう既にこの振る舞いで ver.3.0 として安定版リリースされていることもあるだろうから、今更 ver.2.x と同じ動作に戻すことも無理があろう。
しかしである。
ちょっと問題視してる点、というかホントに困った点は、いろいろ調べた限りでは、どうも、"所有されているエンティティ型" に含まれるプロパティを、明示的に非 null 許容としてデータベース生成する方法がなさそうなのだ。
あちこちに Required 属性をつけたり、OnModelCreating() メソッド内で fluent API であれこれモデルの指定を行なっても、 "所有されているエンティティ型" に含まれるプロパティが null 許容になってしまう動作は変えられなかった。
唯一の回避策としては、データベースマイグレーションの仕組みを使ってデータベース自動生成の処理を C# ソースファイルとして生成し、その自動生成されたマイグレーション用の C# ソースファイルを手で編集して、nullable: true になってる箇所を nullable: false に書き直してくれ、というのである (出典は下記)。
![d0079457_19544615.png]()
これはちょっとあんまりではないかと個人的には思われるのだが、どうだろうか。
EFCore は Apache License 2.0 のオープンソース製品であり、リポジトリは GitHub にある。
そして EFCore の GitHub リポジトリ上には本件に関する Issue があがっており(下記 #12100)、いちおう EFCore の開発チームはこの問題(?)を承知しているようではある。
また、必要な人はこの Issue (#12100) に投票しよう、との呼びかけもある。
![d0079457_19544652.png]()
とはいえ、1,000 を超える Issue が開かれたままであり、また、この問題についての Issue についての Vote (投票) 数も目立って多いとはいえず、近い将来の内にも何かしらの対応がなされるのかどうかはあまり期待が持てないでいる。
最後に
この問題(?)を再現するソースコード一式は下記にて公開している。
EFCore の ver.3.x は、ver.2.x に比べていろいろ魅力的な改善が施されており、早く移行したいところではある。
しかし自分がたずさわっているプロダクトではこの件がネックになって、未だに EFCore ver.2.x を使用したまま足踏みしている。
同じ悩みを抱えている方々はいないのだろうか、あるいは、どのように回避されているのであろうか、気になる次第。
.NET プログラミングにおけるデータベースアクセスライブラリの定番である、EntityFrame Core (以下、EFCore)。
EFCore を用いたデータベースを読み書きするプログラム開発において用いられる技法のひとつとして、"Code First" と呼ばれる作り方がある。
すなわち、EFCore を基盤とした C# ソースコードとしてデータベースのテーブル構造を記述してプログラムをビルドし、そのプログラムの実行によって、データベースとその構造を自動生成するというものだ。
自分は趣味/仕事の双方で、この "Code First" スタイルのプログラム開発を行なっている。
さてそのような "Code First" スタイルでの EFCore プログラミングにおいて、"所有されているエンティティ型" という機能が EFCore に用意されている。
これは何か、ちょっとサンプルコードを示してみよう。
まず、データベース上で People テーブルにマップすることにする、Person クラスというのを C# ソースコードとして記述する。
public class Person {
public int Id { get; set; }
[Required]
public string Name {get; set; }
public double Height { get; set; }
public double Weight { get; set; }
}
ところで、このプログラム開発において、Height と Weight の 2つのプロパティのペアが、この Person クラスに限らず他の多数のクラス (=テーブル) でも頻出するとしよう。
その場合、Height と Weight の 2つのプロパティを持つ独立したクラスを "所有されているエンティティ型" として作成し (ここでは Metric クラスとする) 各所で再利用できるようになる。
public class Person {
public int Id { get; set; }
[Required]
public string Name {get; set; }
public Metric Metric { get; set; }
}
[Owned] // <-- "所有されているエンティティ型" であることを記す
public class Metric {
public double Height { get; set; }
public double Weight { get; set; }
}
こうして実装したプログラムを実行し、データベースを自動生成されると、次のような People テーブルを作成してくれるのである。

"所有されているエンティティ型" を使った実装について、このサンプルコードだと今ひとつ効能がわかりにくいかもしれない。
しかし、同じセンサーデータ構成をセンサーの数だけ同一レコード上に列として並べたい場合など、効率良く且つミスなく実装できるので便利なのである。
なお、"所有されているエンティティ型" は他にもいくつかその自動生成されるデータベース構造にバリエーションがあるのだが、自分はもっぱら上記サンプルコードで示したようなパターンで利用している。
その他、"所有されているエンティティ型" について詳しくは、公式ドキュメントを参照されたし。
EFCore の v2.x と v.3.0 とで生成されるテーブル構造が異なる!
...と、まぁ、このような "所有されているエンティティ型" を活用したプログラム開発を、EFCore の ver.2.1~2.2 ベースで行なってきた。
そしてあくる日。
EFCore の次バージョン、ver.3.0 がリリースされたので、手持ちのプロジェクトの EFCore のバージョンを、ver.2.x から ver.3.0 に更新した。
そして動作を確認していたところ、"所有されているエンティティ型" に関して、自動生成されるテーブル構造が変わっていることに気がついた。
まずおさらいだが、EFCore v2.x においては、最初に示したサンプルコードだと、Height および Weight は、C# コード上は double 型なので、自動生成されるデータベース上の型は float NOT NULL というように非 null 許容となっている。
(以下にデータベース構造の図を再掲する)

これは少なくとも自分にとっては直感と相違ない振る舞いである。
もしデータベース上の型を null 許容としたければ、C# コード側を double? と記述することになる。
ところが、である。
EFCore v3.0 だと、同じコードからデータベースを自動生成するようにすると、なんと、C# コード上、"所有されているエンティティ型" に含まれる double 型が、データベース上では float NULL というように null 許容になってしまったのだ(下図)。

これが、"所有されているエンティティ型" に含まれるプロパティではなく、テーブルにマップされるクラス直属のプロパティであれば、これまでと変わらない振る舞いとなる。
すなわち、C# コードの double 型はデータベース上の float NOT NULL に、double? 型は float NULL となるようにデータベース構造が生成される。
この "所有されているエンティティ型" に含まれるプロパティが null 許容になってしまう振る舞いは、現時点での最新バージョン (但し安定版ではない) である v.3.1 Preview 3 や、安定版の最新パッチバージョン v.3.0.1 でも同じであった。
破壊的変更があるのは仕方がないが...
まぁ、EFCore も v.2 から v3 へとメジャーバージョンがあがるくらいなので、破壊的変更があるのは仕方がないとは思う。
また、もう既にこの振る舞いで ver.3.0 として安定版リリースされていることもあるだろうから、今更 ver.2.x と同じ動作に戻すことも無理があろう。
しかしである。
ちょっと問題視してる点、というかホントに困った点は、いろいろ調べた限りでは、どうも、"所有されているエンティティ型" に含まれるプロパティを、明示的に非 null 許容としてデータベース生成する方法がなさそうなのだ。
あちこちに Required 属性をつけたり、OnModelCreating() メソッド内で fluent API であれこれモデルの指定を行なっても、 "所有されているエンティティ型" に含まれるプロパティが null 許容になってしまう動作は変えられなかった。
唯一の回避策としては、データベースマイグレーションの仕組みを使ってデータベース自動生成の処理を C# ソースファイルとして生成し、その自動生成されたマイグレーション用の C# ソースファイルを手で編集して、nullable: true になってる箇所を nullable: false に書き直してくれ、というのである (出典は下記)。

これはちょっとあんまりではないかと個人的には思われるのだが、どうだろうか。
EFCore は Apache License 2.0 のオープンソース製品であり、リポジトリは GitHub にある。
そして EFCore の GitHub リポジトリ上には本件に関する Issue があがっており(下記 #12100)、いちおう EFCore の開発チームはこの問題(?)を承知しているようではある。
また、必要な人はこの Issue (#12100) に投票しよう、との呼びかけもある。

とはいえ、1,000 を超える Issue が開かれたままであり、また、この問題についての Issue についての Vote (投票) 数も目立って多いとはいえず、近い将来の内にも何かしらの対応がなされるのかどうかはあまり期待が持てないでいる。
最後に
この問題(?)を再現するソースコード一式は下記にて公開している。
EFCore の ver.3.x は、ver.2.x に比べていろいろ魅力的な改善が施されており、早く移行したいところではある。
しかし自分がたずさわっているプロダクトではこの件がネックになって、未だに EFCore ver.2.x を使用したまま足踏みしている。
同じ悩みを抱えている方々はいないのだろうか、あるいは、どのように回避されているのであろうか、気になる次第。