Entity Framework Coreでリレーションシップを設定する方法
2022-11-07
azblob://2022/11/11/eyecatch/2022-11-07-entity-framework-core-relationship-000.jpg

こんにちは、あおいです。来世に生まれ変わるなら、外銀トレーダーとしてMAKE MONEYしたい!!!MDになりたい!!!(なれねーよ) 

さて、Entity Framework Coreでリレーションシップを設定するとき、設定方法がよく分からず詰まってしまう方がいらっしゃると思います。 

そこで、今回はEntity Framework Coreで1対1・1対多・多対多の各リレーションシップの設定方法を紹介したいと思います。

 

1対1

以下のBookテーブルとAuthorテーブルで、1冊の書籍に1人の著者が紐づいていると仮定します。まぁ、複数人の著者のケースもありますが、今回は一旦スルーとしておきます。

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int Price { get; set; }
}

public class Author
{
    public int Id { get; set; }
    public string Name { get; set; }
}

 

まず、各テーブルにナビゲーションプロパティと外部キーを記述します。

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int Price { get; set; }

 // ナビゲーションプロパティ
 public Author Author { get; set; }  
}

public class Author
{
    public int Id { get; set; }
    public string Name { get; set; }

 // 外部キー
 public int BookId { get; set; }

 // ナビゲーションプロパティ
 public Book Book { get; set; }
}

 

次に、DbContext.csのOnModelCreatingで、FluentAPIを使ってリレーションシップを設定します。おそらく、こちらのFluentAPIの部分で混乱してしまう方がいらっしゃると思いますが、次のように置き換えて考えてみてください。

①modelBuilder.Entity<T>・・・ジェネリック型に親テーブルとなるBookを設定する。②HasOne(b => b.Author)・・・Book(書籍)は1人の著者(Author)が存在し、③WithOne(a => a.Book)・・・著者は1冊の書籍を執筆している。④HasForeignKey<T>・・・ジェネリック型に外部キーを保持するAuthorを設定する。

protected override void OnModelCreating (ModelBuilder modelBuilder)
{
    // 1対1のリレーションシップ
    modelBuilder.Entity<Book>(entity =>
    {
        // 書籍は1人の著者が存在し、著者は1つの書籍を執筆している
        entity.HasOne(b => b.Author)
        .WithOne(a => a.Book)
        .HasForeignKey<Author>(a => a.BookId)
        .HasConstraintName("book_fkey"); // 外部キーの名称を任意で設定する
    });

 

1対多

以下のCompanyテーブルとEmployeeテーブルで、1つの会社に複数人の会社員が紐づいていると仮定します。

public class Company
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
}

 

まず、各テーブルにナビゲーションプロパティと外部キーを記述します。1つの会社には複数人の会社員が存在するので、CompanyクラスのナビゲーションプロパティをICollection型にします。

public class Company
{
    public int Id { get; set; }
    public string Name { get; set; }

 // ナビゲーションプロパティ
 public ICollection<Employee> Employees { get; set; }  
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }

 // 外部キー
 public int CompanyId { get; set; }

 // ナビゲーションプロパティ
 public Company Company { get; set; }
}

 

次に、DbContext.csのOnModelCreatingで、FluentAPIを使ってリレーションシップを設定します。おそらく、こちらのFluentAPIの部分で混乱してしまう方がいらっしゃると思いますが、次のように置き換えて考えてみてください。

①modelBuilder.Entity<T>・・・ジェネリック型に親テーブルとなるCompanyを設定する。②HasMany(c => c.Employees)・・・Company(会社)は複数人の会社員(Employees)存在し、③WithOne(e => e.Comany)・・・それぞれの会社員は1つの会社に所属している。④HasForeignKey(e => e.CompanyId)・・・外部キーを指定する。

protected override void OnModelCreating (ModelBuilder modelBuilder)
{
    // 1対多のリレーションシップ
    modelBuilder.Entity<Company>(entity =>
    {
        // 会社は複数の会社員が存在し、それぞれの会社員は1つの会社に所属している
        entity.HasMany(c => c.Employees)
        .WithOne(e => e.Company)
        .HasForeignKey(e => e.CompanyId)
        .HasConstraintName("company_fkey"); // 外部キーの名称を任意で設定する
    });
}

 

多対多

 

以下のBookテーブルとCategoryテーブルで、1冊の書籍は複数のカテゴリーに表示され、1つのカテゴリーには多数の書籍が含まれていると仮定します。

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int Price { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

 

多対多のリレーションシップは直接設定することができないので、通常のテーブル設計に倣ってBookCategoryの中間テーブルを追加し、プロパティには各テーブルの主キーとナビゲーションプロパティを記述します。

 

また、Book・Categoryテーブルにも中間テーブルのBookCategoryに対して、ICollection型のナビゲーションプロパティを設定します。

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int Price { get; set; }
    public ICollection<BookCategory> BookCategories { get; set; }
}  

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<BookCategory> BookCategories { get; set; }
}  

// 中間テーブルを追加
public class BookCategory
{
 // Bookテーブルの主キーとナビゲーションプロパティ
    public int BookId { get; set; }
    public Book Book { get; set; }

 // Categoryテーブルの主キーとナビゲーションプロパティ
    public int CategoryId { get; set; }
    public Category Category { get; set; }
}

 

次に、DbContext.csのOnModelCreatingで、FluentAPIを使ってリレーションシップを設定します。おそらく、こちらのFluentAPIの部分で混乱してしまう方がいらっしゃると思いますが、次のように置き換えて考えてみてください。

①modelBuilder.Entity<T>・・・ジェネリック型に中間テーブルとなるBookCategoryを設定する。②HasKey(bc => new { bc.BookId, bc.CategoryId})・・・中間テーブルの主キーは、両方の外部キー値で構成される複合主キーとなる。①modelBuilder.Entity<T>・・・ジェネリック型に中間テーブルとなるBookCategoryを設定する。②HasOne(bc => bc.Book)・・・BookCategoryは1つの書籍を保持し、③WithMany(b => b.BookCategories)・・・1つの書籍は複数のBookCategoryを保持する。④HasForeignKey(bc => bc.BookId)・・・外部キーを設定する。①modelBuilder.Entity<T>・・・ジェネリック型に中間テーブルとなるBookCategoryを設定する。②HasOne(bc => bc.Category)・・・BookCategoryは1つのカテゴリーを保持し、③WithMany(b => b.BookCategories)・・・1つのカテゴリーは複数のBookCategoryを保持する。④HasForeignKey(bc => bc.CategoryId)・・・外部キーを設定する。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
 // 多対多のリレーションシップ
    modelBuilder.Entity<BookCategory>()
        .HasKey(bc => new { bc.BookId, bc.CategoryId });  // HasKeyメソッドで複合主キーを設定

    modelBuilder.Entity<BookCategory>()
        .HasOne(bc => bc.Book)
        .WithMany(b => b.BookCategories)
        .HasForeignKey(bc => bc.BookId);  

    modelBuilder.Entity<BookCategory>()
        .HasOne(bc => bc.Category)
        .WithMany(c => c.BookCategories)
        .HasForeignKey(bc => bc.CategoryId);
}

 

最後に、中間テーブルのBookCategoryをコンテキストに追加します。DBへのデータの挿入・読み取り・更新・削除といった基本的な操作はDbContextを通じて行います。DbContextは内部に挿入や更新を行うオブジェクトの状態を保持しています。

public class SampleDbContext : DbContext
{
    public DbSet<Book> Books { get; set; }
    public DbSet<Category> Categories { get; set; }
    public DbSet<BookCategory> BookCategories { get; set; }
}

今回はEntity Framework Coreでリレーションシップを設定する方法について紹介させていただきました。少しでもお役に立てれば幸いです。参考記事リレーションシップ - EF Core | Microsoft DocsDocumentation and Tutorials | Learn Entity Framework Core