0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Entity Framework6】HasRequiredとHasOptionalの使い分け方【リレーション】

Posted at

はじめに

現在、とあるプロジェクトでEntity Framework6を使用していています。
Entity Frameworkはなかなか便利なのですが、他のORマッパーと比較し、ちょいちょい独特な点があります。
(特に、最近までLaravelを中心に開発していたので、かなり使いにくく感じる点はあります)

特に厄介なのは、リレーション周り。
リレーションは、以下のように構成します。

image.png

ContextのOnModelCreatingに、リレーションを一通り書くことになります。
ここで、1:nのリレーションの書き方で、かなり迷うことになりました。

いまだにEFCoreではなく、EF6を使用している方は少ないかもしれませんが、ここではそんな希少価値の高い方々向けに、
リレーション周りについて、まとめていくことにしました。

例のテーブル構成

CREATE TABLE [dbo].[テーブル11](
	[主キー] [int] PRIMARY KEY NOT NULL,
	[値] [nvarchar](128) NULL
);

CREATE TABLE [dbo].[テーブル12](
	[主キー] [int] PRIMARY KEY NOT NULL,
	[外部キー] [int] NOT NULL,
	[値] [nvarchar](128) NULL
);


CREATE TABLE [dbo].[テーブル13](
	[主キー] [int] PRIMARY KEY NOT NULL,
	[外部キー] [int] NULL,
	[値] [nvarchar](128) NULL
);




CREATE TABLE [dbo].[テーブル31](
	[主キー] [int] PRIMARY KEY NOT NULL,
	[値] [nvarchar](128) NULL
);

CREATE TABLE [dbo].[テーブル32](
	[主キー] [int]  NOT NULL,
	[連番] [int] NOT NULL,
	[値] [nvarchar](128) NULL,
	primary key (主キー, 連番)
);


ER図

そのうち

HasRequiredとHasOptionalの使い分け

1:nリレーションを構築する際に、HasRequiredもしくはHasOptionalというメソッドを使用します。

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Table12>() // 子テーブル
              .HasRequired(x => x.Table11) // 親テーブルへの参照
              .WithMany(x => x.Table12s) // 子テーブルへの参照
              .HasForeignKey(x => new { x.Gaibuki }); // 子テーブル側の、外部キーの設定

            modelBuilder.Entity<Table23>() // 子テーブル
              .HasOptional(x => x.Table21) // 親テーブルへの参照
              .WithMany(x => x.Table23s) // 子テーブルへの参照
              .HasForeignKey(x => new { x.Gaibuki }); // 子テーブル側の、外部キーの設定
        }

このメソッドの使い分けについて、記載していきます。

HasRequired:そのキーが必須の場合 HasOptional:そのキーが必須でない場合

1:nのリレーションを行う場合でも、以下のようなケースがあると思います。

  • 必ず、1:nの親子関係が組まれる場合。例:「契約」と「契約明細」という1:nテーブルがある場合、「契約」の外部キーをNULLとして「契約明細」のデータが作成されることは無い(契約明細のみ単独で作ることは無い)ので、「契約」への外部キーは必須となる。

  • 1:nの親子関係が組まれないケースがある場合。例:「契約」というテーブルに「削除ユーザー」という列があり、削除ユーザーのIDを入れることによって、「ユーザー」テーブルへのリレーションが行われる場合。削除されている場合のみidをセットするため、必須ではない。

このように、「nは、1へのリレーションが必ず行われる場合」と「nは、1へのリレーションが無くてもデータ作成が可能な場合」の2種類あることが分かります。
この使い分けが、HasRequiredとHasOptionalの使い分けです。

制約

HasRequiredを使用する場合

・外部キーに設定した列は、null非許容型である必要がある
→int?のようなnull許容型は使用できません。なぜなら、親テーブルへの参照が必須となるためです。
よって、intのようなnull非許容型である必要があります。

HasOptionalを使用する場合

・外部キーに設定した列は、null許容型である必要がある?
→int?のようなnull許容型が使用できます。また、ここは要調査なのですが、intのようなnull非許容型も、使用できるかもしれません。

・外部キーに設定した列に、Required属性は設定できない
→外部キーに設定した列に、Required属性は設定できないようです。

以下のようなModel構成があったとします。
テーブル12は必須リレーション、テーブル13は任意リレーションです。

 /// <summary>
    /// テーブル11
    /// </summary>
    [Table("テーブル11")]
    [Serializable]
    public partial class Table11
    {
        /// <summary>
        /// 主キー
        /// </summary>
        [Column("主キー")]
        [Display(Name = "主キー")]
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int? Syuki { get; set; }

        /// <summary>
        /// 値
        /// </summary>
        [Column("値")]
        [Display(Name = "値")]
        public string Atai { get; set; }

        public virtual ICollection<Table12> Table12s { get; set; }
        public virtual ICollection<Table13> Table13s { get; set; }
    }

    /// <summary>
    /// テーブル12
    /// </summary>
    [Table("テーブル12")]
    [Serializable]
    public partial class Table12
    {
        /// <summary>
        /// 主キー
        /// </summary>
        [Column("主キー")]
        [Display(Name = "主キー")]
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int? Syuki { get; set; }

        /// <summary>
        /// 外部キー
        /// </summary>
        [Column("外部キー")]
        [Display(Name = "外部キー")]
        public int Gaibuki { get; set; }

        /// <summary>
        /// 値
        /// </summary>
        [Column("値")]
        [Display(Name = "値")]
        public string Atai { get; set; }

        public virtual Table11 Table11 { get; set; }
    }

    /// <summary>
    /// テーブル13
    /// </summary>
    [Table("テーブル13")]
    [Serializable]
    public partial class Table13
    {
        /// <summary>
        /// 主キー
        /// </summary>
        [Column("主キー")]
        [Display(Name = "主キー")]
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int? Syuki { get; set; }

        /// <summary>
        /// 外部キー(null許可)
        /// </summary>
        [Column("外部キー")]
        [Display(Name = "外部キー")]
        public int? Gaibuki { get; set; }

        /// <summary>
        /// 値
        /// </summary>
        [Column("値")]
        [Display(Name = "値")]
        public string Atai { get; set; }

        public virtual Table11 Table11 { get; set; }
    }

この場合、以下のようなリレーション設定となります。
テーブル12は必須リレーションなのでHasOptionalはOK、テーブル13は任意リレーションなのでHasOptionalはOKです。

            // OK
            modelBuilder.Entity<Table12>()
              .HasRequired(x => x.Table11)
              .WithMany(x => x.Table12s)
              .HasForeignKey(x => new { x.Gaibuki });

            // ダメ
            //modelBuilder.Entity<Table12>()
            //  .HasOptional(x => x.Table11)
            //  .WithMany(x => x.Table12s)
            //  .HasForeignKey(x => new { x.Gaibuki });


            // OK
            modelBuilder.Entity<Table13>()
              .HasOptional(x => x.Table11)
              .WithMany(x => x.Table13s)
              .HasForeignKey(x => new { x.Gaibuki });

            // これもOK
            //modelBuilder.Entity<Table13>()
            //  .HasRequired(x => x.Table11)
            //  .WithMany(x => x.Table13s)
            //  .HasForeignKey(x => new { x.Gaibuki });

1:nのn側のテーブルが、複合キーの場合は、HasRequired

1:nのn側のテーブルで、以下のような複合主キー設定が行われている場合があります。今回の例でいうと、テーブル32が該当します。

  • 1側のテーブルの主キー
  • 連番

この場合、外部キーは「1側のテーブルの主キー」になります。
そしてこの場合、この主キーには値が必ず入ってくるので、HasRequiredとなります。

その他

細かいことはまた後ほど!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?