星期六, 1月 06, 2024

[EFCore] Migration - Foreign Key

延續 [EFCore] Migration 內容,要使用 Migration 來建立 FK,商業邏輯會是衣服、衣服規格和狀態,衣服本身會有狀態 (EX:停售),衣服尺寸也需要狀態 (EX:停售、缺貨),ER 圖如下
閱讀 Data Annotations - ForeignKey Attribute in EF 6 & EF Core 時發現,Model 怎麼設計會影響定義 FK 方式,該筆記基本就是把 FK 建出來,沒有特別深究優劣

建立 Model 

Model:Clothes、ClothesDetail 和 Status
FK:
  • Clothes - Status:1 對 1
  • ClothesDetail - Status:1 對 1
  • Clothes - ClothesDetail:1 對多

Mode Code 如下
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreMigration.Models
{
    public class Clothes
    {
        public int ClothesID { get; set; }

        public string ClothesName { get; set; }

        public int StatusID { get; set; }

        [ForeignKey("StatusID")]
        public Status Status { get; set; }

        public ICollection<ClothesDetail> ClothesDetail { get; set; }
    }
    
    public class ClothesDetail
    {
        public int ClothesDetailID { get; set; }

        public int ClothesID { get; set; }

        public int StatusID { get; set; }

        [ForeignKey("StatusID")]
        public Status Status { get; set; }

        public Clothes Clothes { get; set; }
    }
    
    public class Status
    {
        public int StatusID { get; set; }

        public string StatusName { get; set; }
    }    
}

以 [1 對 1] 的 Clothes 和 Status 來說,Clothes model 內有 StatusID Property 和 Status Class 屬性,透過 Data Annotations ForeignKeyAttribute 來進行 FK 設定
namespace EFCoreMigration.Models
{
    public class Clothes
    {
        public int ClothesID { get; set; }

        public string ClothesName { get; set; }

        public int StatusID { get; set; }

        [ForeignKey("StatusID")]
        public Status Status { get; set; }
    }
}


以 [1 對多] 的 Clothes 和 ClothesDetail 來說,Clothes model 內有 ICollection 的 ClothesDetail,ClothesDetail model 內有 Clothes Property,透過 Fluent API 來指定 FK
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreMigration.Models
{
    public class Clothes
    {
        public int ClothesID { get; set; }

        public string ClothesName { get; set; }

        public ICollection<ClothesDetail> ClothesDetail { get; set; }
    }
    
    public class ClothesDetail
    {
        public int ClothesDetailID { get; set; }

        public int ClothesID { get; set; }

        public Clothes Clothes { get; set; }
    }
    
    public class MigrationDbContext : DbContext
    {
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Clothes>()
                .HasMany(c => c.ClothesDetail)
                .WithOne(cd => cd.Clothes)
                // 關閉 Delete Cascade
                .OnDelete(DeleteBehavior.NoAction);

            base.OnModelCreating(modelBuilder);
        }
    }

}

Migration 設定檔案內容

為節省版面,Migration 設定檔案內容就以 ClothesDetail 為主而已,想記錄重點有下述兩點
  • migration 時 FK 欄位會自動建立 Index
  • Data Annotations 設定的 FK,預設會開啟 Delete Cascade 功能,嘗試透過 Data Annotations 關閉 Delete Cascade,但在 EF Core OnDelete 內提及 Data Annotations 沒有關閉 Delete Cascade 功能,網路上大多也都是透過 Fluent API 來進行關閉
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace EFCoreMigration.Migrations
{
    /// <inheritdoc />
    public partial class AddFKLab : Migration
    {
        /// <inheritdoc />
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "ClothesDetail",
                columns: table => new
                {
                    ClothesDetailID = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    ClothesID = table.Column<int>(type: "int", nullable: false),
                    StatusID = table.Column<int>(type: "int", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_ClothesDetail", x => x.ClothesDetailID);
                    table.ForeignKey(
                        name: "FK_ClothesDetail_Clothes_ClothesID",
                        column: x => x.ClothesID,
                        principalTable: "Clothes",
                        principalColumn: "ClothesID");
                    table.ForeignKey(
                        name: "FK_ClothesDetail_Status_StatusID",
                        column: x => x.StatusID,
                        principalTable: "Status",
                        principalColumn: "StatusID",
                        // 開啟 Cascade Delete 功能
                        onDelete: ReferentialAction.Cascade);
                });

            // 建立 Index
            migrationBuilder.CreateIndex(
                name: "IX_ClothesDetail_StatusID",
                table: "ClothesDetail",
                column: "StatusID");
        }
    }
}

沒有留言:

張貼留言