【EFCore】 複合外部キーの書き方

ここにすべて載ってます。

以上

・・・

だとさすがにあれなので、説明します

 

スポンサーリンク

複合外部キーの書き方(概要)

複合キーなので、以下のことをする必要があります。

  • DbContextクラスOnModelCreating時に、modelBuilderでキーの組み合わせを設定する
  • ※ナビゲーションプロパティを記述する方法と省略する方法がある。

以下、実際にコードを見ながら、詳細にみていきます。

、ブログと投稿記事

まず、以下の関係をよく頭に入れてください。

Blog:ブログ
Post:投稿記事

  • ブログA
    • 投稿1
    • 投稿2
    • 投稿3
  • ブログB
    • 投稿1
    • 投稿2

それでは、実際にコードを見ていきましょう。

例1. ナビゲーションプロパティとmodelBuilderの両方を記述したパターン

以下の二つの手順で実現できます。

  • Step1:ナビゲーションプロパティの記述
  • Step2:modelBuilderで、複合外部キーの設定
Step1.ナビゲーションプロパティを記述する
//ブログ(テーブル定義)
public class Blog
{
  //例、BlogIdとUrlで、投稿記事を制約する
  public int BlogId { get; set; }
  public string Url { get; set; }

  //ナビゲーションプロパティ(Navigation Property)
  public List<Post> Posts { get; set; } //ブログは投稿記事のリストを持つ
}

//投稿記事(テーブル定義)
public class Post
{
  public int PostId { get; set; } //暗黙的なPrimaryKey
  public string Title { get; set; }
  public string Content { get; set; }

  public int BlogId { get; set; } //外部キーを用意
  public string Url { get; set; } //外部キーを用意

  //ナビゲーションプロパティ(Navigation Property)
  public Blog Blog { get; set; } //投稿記事はブログに所属する。
}
ナビゲーションプロパティとは?
関係のあるデータへの参照を保持するためのデータ領域
外部制約を使うとなると、制約する側とされる側で、お互いのことを知っておく必要がある。そのためのデータ領域てきな感じ(だと思います。詳しくは公式を)

 

Step2.modelBuilderで、複合外部キーの設定を行う
class CompositeForeignContext : DbContext
{
  //テーブルのキー設定など記述する
  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    //Blogの設定
    modelBuilder.Entity<Blog>()
      .HasKey(blog => new { blog.BlogId, blog.Url }); //複合PrimaryKeyの設定

    //Postの設定
    modelBuilder.Entity<Post>()
      .HasOne(post => post.Blog) //Blogを参照する。
      .WithMany(blog => blog.Posts) //Blogに対し、Postは複数存在する。
      .HasForeignKey(post => new { post.BlogId, post.Url }); //外部制約キーの指定
        //(暗黙的にBlogのPrimaryKeyが外部キーとなる)
}
}

以上です。

例1.まとめ

まとめるとソースコードは以下のようになります。

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;

class CompositeForeignContext : DbContext
{
  public DbSet<Blog> Blogs { get; set; }
  public DbSet<Post> Posts { get; set; }

  //コンストラクタ
  public CompositeForeignContext(DbContextOptions<CompositeForeignContext> options)
    : base(options)
  {
  }

  //ブログ(テーブル定義)
  public class Blog
  {
    //例、BlogIdとUrlで、投稿記事を制約する
    public int BlogId { get; set; }
    public string Url { get; set; }

    //ナビゲーションプロパティ(Navigation Property)
    public List<Post> Posts { get; set; } //ブログは投稿記事のリストを持つ
  }

  //投稿記事(テーブル定義)
  public class Post
  {
    public int PostId { get; set; } //暗黙的なPrimaryKey
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; } //外部キーを用意
    public string Url { get; set; } //外部キーを用意

    //ナビゲーションプロパティ(Navigation Property)
    public Blog Blog { get; set; } //投稿記事はブログに所属する。
  }

  //テーブルのキー設定など記述する
  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    //Blogの設定
    modelBuilder.Entity<Blog>()
      .HasKey(blog => new { blog.BlogId, blog.Url }); //複合PrimaryKeyの設定

    //Postの設定
    modelBuilder.Entity<Post>()  //Postの設定
      .HasOne(post => post.Blog) //Blogを参照する。
      .WithMany(blog => blog.Posts) //Blogに対し、Postは複数存在する。
      .HasForeignKey(post => new { post.BlogId, post.Url }); //外部制約キーの指定。
        //(暗黙的にBlogのPrimaryKeyが外部キーとなる)
  }
}

例2.ナビゲーションプロパティを省略したパターン

ナビゲーションプロパティの記述は実は省略できます。
modelBuilderで、複合外部キーの設定だけを行います。

class CompositeForeignNoNavContext : DbContext
{
  //テーブルのキー設定など記述する
  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    //Blogの設定
    modelBuilder.Entity<Blog>()
      .HasKey(blog => new { blog.BlogId, blog.Url }); //複合PrimaryKeyの設定

    //Postの設定
    modelBuilder.Entity<Post>()
      .HasOne<Blog>() //Blogを参照する。
      .WithMany()     //Blogに対し、Postは複数存在する。
      .HasForeignKey(post => new { post.BlogId, post.Url }); //複合外部キーの指定
        //(暗黙的にBlogのPrimaryKeyが割り当てられる)
  }
}
HasOne()とWithMany()の記述の仕方が、ナビゲーションプロパティありの場合と変わります。

ソース全体はこんな感じ

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;

class CompositeForeignNoNavContext : DbContext
{
  public DbSet<Blog> Blogs { get; set; }
  public DbSet<Post> Posts { get; set; }

  //コンストラクタ
  public CompositeForeignNoNavContext(DbContextOptions<CompositeForeignNoNavContext> options)
    : base(options)
  {
  }

  //ブログ(テーブル定義)
  public class Blog
  {
    //例、BlogIdとUrlで、投稿記事を制約する
    public int BlogId { get; set; }
    public string Url { get; set; }
  }

  //投稿記事(テーブル定義)
  public class Post
  {
    public int PostId { get; set; } //暗黙的なPrimaryKey
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; } //外部キーを用意
    public string Url { get; set; } //外部キーを用意
  }

  //テーブルのキー設定など記述する
  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    //Blogの設定
    modelBuilder.Entity<Blog>()
      .HasKey(blog => new { blog.BlogId, blog.Url }); //複合PrimaryKeyの設定

    //Postの設定
    modelBuilder.Entity<Post>()
      .HasOne<Blog>() //Blogを参照する。
      .WithMany()     //Blogに対し、Postは複数存在する。
      .HasForeignKey(post => new { post.BlogId, post.Url }); //複合外部キーの指定
        //(暗黙的にBlogのPrimaryKeyが割り当てられる)
  }
}
スポンサーリンク

生成されるMigrationsファイルについて

「add-migration」を行うと以下のファイルが生成されました。

public partial class CompositeForeignContext : Migration
{
  protected override void Up(MigrationBuilder migrationBuilder)
  {
    migrationBuilder.CreateTable(
      name: "Blogs",
      columns: table => new
      {
        BlogId = table.Column<int>(nullable: false),
        Url = table.Column<string>(nullable: false)
      },
      constraints: table =>
      {
        table.PrimaryKey("PK_Blogs", x => new { x.BlogId, x.Url });
    });

    migrationBuilder.CreateTable(
      name: "Posts",
      columns: table => new
      {
        PostId = table.Column<int>(nullable: false)
          .Annotation("SqlServer:Identity", "1, 1"),
        Title = table.Column<string>(nullable: true),
        Content = table.Column<string>(nullable: true),
        BlogId = table.Column<int>(nullable: false),
        Url = table.Column<string>(nullable: true)
      },
      constraints: table =>
      {
        table.PrimaryKey("PK_Posts", x => x.PostId);
        table.ForeignKey(
          name: "FK_Posts_Blogs_BlogId_Url",
          columns: x => new { x.BlogId, x.Url },
          principalTable: "Blogs",
          principalColumns: new[] { "BlogId", "Url" },
          onDelete: ReferentialAction.Restrict);
      });

    migrationBuilder.CreateIndex(
      name: "IX_Posts_BlogId_Url",
      table: "Posts",
      columns: new[] { "BlogId", "Url" });
  }

  protected override void Down(MigrationBuilder migrationBuilder)
  {
    migrationBuilder.DropTable(
      name: "Posts");

    migrationBuilder.DropTable(
      name: "Blogs");
  }
}

ちゃんと複合外部キーが設定できているようです。

ちなみにナビゲーションプロパティの省略ありなしで、生成されるmigrationファイルを比較してみましたが、差はありませんでした。

 

コメント

タイトルとURLをコピーしました