Entity Framework Core - Owned typesのサンプル
EF Coreの「Owned types」を試してみました。日本語訳はどうなるんでしょう。所有型?かな?
What is new in EF Core 2.0 - EF Core | Microsoft Docs
「Owned types」はEF6の複合型に似た機能のようで、テーブルにある複数カラムをプロパティクラスにマッピングするものです。
ということで早速。次のテーブルがあるとします。
-- テーブル作成 drop table if exists dbo.Character; create table dbo.Character( Id int, Name nvarchar(4) not null, -- 名前 Level int not null, -- レベル Hp int not null, -- HP Mp int not null, -- MP constraint PK_Character primary key(Id) );
次のモデルにマッピングしてみましょう。HpとMpのカラムは再利用したいという体でStatusクラスとしています。
// ステータス public class Status { public int Hp { get; set; } public int Mp { get; set; } } // キャラクター public class Character { public int Id { get; set; } public string Name { get; set; } public int Level { get; set; } // HPとMPを持つステータス public Status Status { get; set; } }
マッピングするにはDBコンテキストのOnModelCreatingメソッド内でOwnsOneメソッドを使ってStatusプロパティを指定します。
OwnsOneメソッドの2つ目の引数では入れ子になったStatusクラスのマッピングも指定できます。マッピングを指定しないとStatusクラスの各プロパティは「Status_Hp」「Status_Mp」といったカラム名にマッピングされるので、下記コードでは実際のカラム名に合うように調整しました。
// DBコンテキストの一部 public class AppDbContext : DbContext { protected override void OnModelCreating(ModelBuilder modelBuilder) { // Characterテーブルへのマッピング modelBuilder.Entity<Character>().ToTable(nameof(Character)); // CharacterがStatusを所有する modelBuilder.Entity<Character>().OwnsOne( character => character.Status, statusBuilder => { // StatusをHpカラムとMpカラムへマッピング // (デフォルトだと「Status_Hp」「「Status_Mp」」といったカラムにマッピングされる) statusBuilder.Property(status => status.Hp).HasColumnName(nameof(Status.Hp)); statusBuilder.Property(status => status.Mp).HasColumnName(nameof(Status.Mp)); }); } }
これで準備が整ったので、うまくマッピングできているか確認してみましょう。
class Program { // 結果出力用 private static void Dump(IEnumerable<Character> characters) { foreach (var character in characters) { Console.WriteLine($"{character.Name}"); Console.WriteLine($"Lv {character.Level}"); Console.WriteLine($"HP {character.Status.Hp}"); Console.WriteLine($"MP {character.Status.Mp}"); Console.WriteLine(); } } static void Main(string[] args) { // データ投入 using (var dbContext = new AppDbContext(tracking: true)) { dbContext.Set<Character>().AddRange( new Character { Id = 1, Name = "エイト", Level = 10, Status = new Status { Hp = 59, Mp = 32 }, }, new Character { Id = 2, Name = "ゼシカ", Level = 10, Status = new Status { Hp = 47, Mp = 28 }, }); dbContext.SaveChanges(); } // 確認 using (var dbContext = new AppDbContext()) { var characters = dbContext.Set<Character>().ToList(); Dump(characters); /* エイト Lv 10 HP 59 MP 32 ゼシカ Lv 10 HP 47 MP 28 */ } // データ更新 using (var dbContext = new AppDbContext(tracking: true)) { // エイトを取得 var character = dbContext.Set<Character>().Find(1); // エイトはレベルがあがった! character.Level += 1; character.Status.Hp += 12; character.Status.Mp += 2; dbContext.SaveChanges(); } // 確認 using (var dbContext = new AppDbContext()) { var characters = dbContext.Set<Character>().ToList(); Dump(characters); /* エイト Lv 11 HP 71 MP 34 ゼシカ Lv 10 HP 47 MP 28 */ } } }
コードとクエリはこちら。