【EF Core】實體的主、從關係

来源:https://www.cnblogs.com/tcjiaan/archive/2023/07/02/17520679.html
-Advertisement-
Play Games

假設有以下兩個實體: public class Student { public int StuID { get; set; } public string? Name { get; set; } public IEnumerable<Homework>? Homeworks { get; set; ...


假設有以下兩個實體:

public class Student
{
    public int StuID { get; set; }
    public string? Name { get; set; }
    public IEnumerable<Homework>? Homeworks { get; set; }
}

public class Homework
{
    public string? Class { get; set; }
    public string? Subject { get; set; }
}

Homework 類表示家庭作業,它並不是獨立使用的,而是與學生類(Student)有依賴關係。一位學生有多個家庭作業記錄,即 Homework 對象用於記錄每位同學的作業的。按照這樣的前提,Student 是主對象,Homework 是從對象。

Student 對象有個 Homeworks 屬性,用於引用 Homework 對象,也就是所謂的“導航屬性”。這個“導航”,估計意思就是你通過這個屬性可以找到被引用的另一個實體對象,所以稱之為導航,就是從 Navigation 的翻譯。

隨後,咱們要從 DbContext 類派生出自定義的資料庫上下文。

public class MyDbContext : DbContext
{
    // 映射的數據表,名稱預設與屬性名稱一樣
    // 即 Students + Works
    public DbSet<Student> Students => Set<Student>();
    public DbSet<Homework> Works => Set<Homework>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // 設置連接字元串
        optionsBuilder.UseSqlServer(Helper.Conn_STRING);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 設置主鍵
        modelBuilder.Entity<Student>().HasKey(s => s.StuID);
        // 建立主從關係
        modelBuilder.Entity<Student>().OwnsMany(s => s.Homeworks);
    }
}

連接字元串是老周事先配置好的,連的是 SQL Server。

public class Helper
{
    public const string Conn_STRING = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=stuDB;Integrated Security=True";
}

用的是 LocalDB,這玩意兒方便。

其實這是一個控制台應用程式,並添加了 Nuget 包。

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.8" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.8" />
  </ItemGroup>

好,回到咱們的代碼中,MyDbContext 重寫了兩個方法:

1、重寫 OnConfiguring 方法,做一些與該 Context 有關的配置,通常是配置連接字元串;也可能配置一下日誌輸出。上面代碼中使用的是擴展方法 UseSqlServer。這就是引用 Microsoft.EntityFrameworkCore.SqlServer Nuget 包的作用。

2、重寫 OnModelCreating 方法。這個是設置實體類相關的模型屬性,以及與數據表的映射,或配置實體之間的關係。上述代碼中,老周做了兩件事:A、為 Student 實體設置主鍵,作為主鍵的屬性是 StuID;B、建立 Student 和 Homework 對象的主從關係,調用 OwnsMany 方法的意思是:一條 Student 記錄對應 N 條 Homework 記錄。因為 Student 類的 Homeworks 屬性是集合。

註意:咱們此處是先建了實體類,運行後才創建資料庫的,所以不需要生成遷移代碼。

在 Main 方法中,咱們要做兩件事:A、根據上面的建模創建資料庫;B、往資料庫中存一點數據。

static void Main(string[] args)
{
    using (var ctx = new MyDbContext())
    {
        //ctx.Database.EnsureDeleted();
        bool res = ctx.Database.EnsureCreated();
        if (res)
        {
            Console.WriteLine("已創建資料庫");
        }
    }

    using(MyDbContext ctx = new())
    {
        // 加點料
        ctx.Students.Add(new Student
        {
            Name = "小張",
            Homeworks = new List<Homework>
            {
                new Homework{ Class = "數學", Subject = "3000道口算題"},
                new Homework{ Class = "英語", Subject = "背9999個單詞"}
            }
        });

        ctx.Students.Add(new Student
        {
            Name = "小雪",
            Homeworks = new Homework[]
            {
                new Homework{ Class = "歷史", Subject = "臨一幅《清明上河圖》"},
                new Homework{ Class = "語文", Subject = "作文題:《百鬼日行》"}
            }
        });

        // 保存
        int x = ctx.SaveChanges();
        Console.WriteLine("共保存了{0}條記錄", x);
    }
}

EnsureCreated 方法會自動創建資料庫。如果不存在資料庫且創建成功,返回 true,否則是 false。資料庫的名稱在連接字元串中配置過。

Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=stuDB;Integrated Security=True

接下來,我們運行一下。稍等幾秒鐘,看到控制台輸出下麵文本就算成功了。

已創建資料庫
共保存了6條記錄

然後,連上去看看有沒有資料庫。

看看,這表的名稱是不是和 MyDbContext 的兩個屬性一樣? 

public class MyDbContext : DbContext
{
    public DbSet<Student> Students => Set<Student>();
    public DbSet<Homework> Works => Set<Homework>();
    ……

你要是不喜歡用這倆名字,也可以發動傳統技能(指老 EF),用 Table 特性給它們另取高名。

[Table("tb_students", Schema = "dbo")]
public class Student
{
   ……
}

[Table("tb_homeworks", Schema = "dbo")]
public class Homework
{
    ……
}

刪除資料庫,再運行一次程式,然後再登錄資料庫看看,表名變了嗎?

那有伙伴們會問:有沒有現代技能?有的,使用 ToTable 方法定義映射的數據表名稱。

先去掉 Student、Homework 類上的 Table 特性,然後直接在重寫 OnModelCreating 方法時配置。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Student>().ToTable("dt_students").HasKey(s => s.StuID);
    modelBuilder.Entity<Homework>().ToTable("dt_works");
    // 建立主從關係
    modelBuilder.Entity<Student>().OwnsMany(s => s.Homeworks);
}

但是這樣寫會報錯的。因為 Homework 實體是 Student 的從屬對象,單獨調用 ToTable 方法在配置的時候會將其設置為獨立對象,而非從屬對象。

所以,正確的做法是在兩個實體建立了從屬性關係後再調用 ToTable 方法(Student 對象是主對象,它可以單獨調用)。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Student>().HasKey(s => s.StuID);
    modelBuilder.Entity<Student>()
        .ToTable("tb_students")
        .OwnsMany(s => s.Homeworks)
        .ToTable("tb_works");
}

因為 Homework 是 Student 的從屬,tb_works 表中要存在一個外鍵——引用 Student.StuID,這樣兩個表才能建立主從關係。如果單獨調用 Entity<Homework>.ToTable 映射表的話,那麼表中不會添加引用 StuID 的外鍵列。就是預設被配置為非主從模式。沒有了外鍵,tb_works 表中存的數據就無法知道是哪位學生的作業了。

這樣創建資料庫後,tb_works 表中就存在名為 StudentStuID 的列,它就是引用 Student.StuID 的外鍵。

CREATE TABLE [dbo].[tb_works] (
    [StudentStuID] INT            NOT NULL,
    [Id]           INT            IDENTITY (1, 1) NOT NULL,
    [Class]        NVARCHAR (MAX) NULL,
    [Subject]      NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_tb_works] PRIMARY KEY CLUSTERED ([StudentStuID] ASC, [Id] ASC),
    CONSTRAINT [FK_tb_works_tb_students_StudentStuID] FOREIGN KEY ([StudentStuID]) REFERENCES [dbo].[tb_students] ([StuID]) ON DELETE CASCADE
);

當然,這個外鍵名字是根據實體類名(Student)和它的主鍵屬性名(StuID)生成的,如果你想自己搞個名字,也是可以的。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Student>().HasKey(s => s.StuID);
    modelBuilder.Entity<Student>()
        .ToTable("tb_students")
        .OwnsMany(s => s.Homeworks, tb =>
        {
            tb.ToTable("tb_works");
            tb.WithOwner().HasForeignKey("student_id");
        });
}

這樣 tb_works 表中就有了名為 student_id 的外鍵。

CREATE TABLE [dbo].[tb_works] (
    [student_id] INT            NOT NULL,
    [Id]         INT            IDENTITY (1, 1) NOT NULL,
    [Class]      NVARCHAR (MAX) NULL,
    [Subject]    NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_tb_works] PRIMARY KEY CLUSTERED ([student_id] ASC, [Id] ASC),
    CONSTRAINT [FK_tb_works_tb_students_student_id] FOREIGN KEY ([student_id]) REFERENCES [dbo].[tb_students] ([StuID]) ON DELETE CASCADE
);

OwnsXXX 方法是指:俺是主表,我要“關照”一下從表;

WithOwner 方法是指:俺是從表,我要配置一下和主表之間建立聯繫的參數(如上面給外鍵另起個名字)。

那麼,我想把兩個表的列全自定義命名,可以嗎?當然可以的。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Student>().HasKey(s => s.StuID);
    modelBuilder.Entity<Student>()
        .ToTable("tb_students", tb =>
        {
            tb.Property(s => s.StuID).HasColumnName("sID");
            tb.Property(s => s.Name).HasColumnName("stu_name");
        })
        .OwnsMany(s => s.Homeworks, tb =>
        {
            tb.ToTable("tb_works");
            tb.WithOwner().HasForeignKey("student_id");
            tb.Property(w => w.Class).HasColumnName("wk_class");
            tb.Property(w => w.Subject).HasColumnName("wk_sub");
        });
}

兩個表的欄位名都變了。

CREATE TABLE [dbo].[tb_students] (
    [sID]      INT            IDENTITY (1, 1) NOT NULL,
    [stu_name] NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_tb_students] PRIMARY KEY CLUSTERED ([sID] ASC)
);

CREATE TABLE [dbo].[tb_works] (
    [student_id] INT            NOT NULL,
    [Id]         INT            IDENTITY (1, 1) NOT NULL,
    [wk_class]   NVARCHAR (MAX) NULL,
    [wk_sub]     NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_tb_works] PRIMARY KEY CLUSTERED ([student_id] ASC, [Id] ASC),
    CONSTRAINT [FK_tb_works_tb_students_student_id] FOREIGN KEY ([student_id]) REFERENCES [dbo].[tb_students] ([sID]) ON DELETE CASCADE
);

註意:Homework 類中沒有定義 Id 屬性(主鍵),它是自動生成的。

 

有大伙伴會想,在 OnModelCreating 方法中建模我頭有點暈,我能不能在定義實體類的時候,直接通過特性批註來實現主從關係呢?那肯定可以的了。

[Table("tb_students")]
[PrimaryKey(nameof(StuID))]
public class Student
{
    [Column("sID")]
    public int StuID { get; set; }

    [Column("st_name")]
    public string? Name { get; set; }

    // 這是導航屬性,不需要映射到數據表
    public IEnumerable<Homework>? Homeworks { get; set; }
}

[Owned]
[Table("tb_homeworks")]
[PrimaryKey(nameof(wID))]
public class Homework
{
    [Column("wk_id")]
    public int wID { get; set; }

    [Column("wk_class")]
    public string? Class { get; set; }

    [Column("wk_sub")]
    public string? Subject { get; set; }

    [ForeignKey("student_id")]  //設置外鍵名稱
    public Student? StudentObj { get; set; }
}

PrimaryKey 特性設置實體類中哪些屬性為主鍵,使用屬性成員的名稱,而不是數據表欄位名稱。

在 Homework 類上用到 Owned 特性,表示其他對象如果引用了 Homework,就會自動建立主從關係—— Homework 為從屬對象。

ForeignKey 特性指定外鍵的名稱。雖然 StudentObj 屬性的類型是 Student 類,但在建立數據表時,只引用了 Student 類的 StuID 屬性。

此時,可以清空 OnModelCreating 方法中的代碼了。

生成的數據表結構與上文差不多。

CREATE TABLE [dbo].[tb_students] (
    [sID]     INT            IDENTITY (1, 1) NOT NULL,
    [st_name] NVARCHAR (MAX) NULL,
    CONSTRAINT [PK_tb_students] PRIMARY KEY CLUSTERED ([sID] ASC)
);

CREATE TABLE [dbo].[tb_homeworks] (
    [wk_id]      INT            IDENTITY (1, 1) NOT NULL,
    [wk_class]   NVARCHAR (MAX) NULL,
    [wk_sub]     NVARCHAR (MAX) NULL,
    [student_id] INT            NULL,
    CONSTRAINT [PK_tb_homeworks] PRIMARY KEY CLUSTERED ([wk_id] ASC),
    CONSTRAINT [FK_tb_homeworks_tb_students_student_id] FOREIGN KEY ([student_id]) REFERENCES [dbo].[tb_students] ([sID])
);

當然了,最好的做法是將特性批註與 OnModelCreating  方法結合使用。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # 1、Java常用插件實現方案 ## 1.2、serviceloader方式 serviceloader是java提供的spi模式的實現。按照介面開發實現類,而後配置,java通過ServiceLoader來實現統一介面不同實現的依次調用。而java中最經典的serviceloader的使用就是J ...
  • **原文鏈接:** [Go 語言 context 都能做什麼?](https://mp.weixin.qq.com/s/7IliODEUt3JpEuzL8K_sOg) 很多 Go 項目的源碼,在讀的過程中會發現一個很常見的參數 `ctx`,而且基本都是作為函數的第一個參數。 為什麼要這麼寫呢?這個參 ...
  • POM( Project Object Model,項目對象模型 ) 是 Maven 工程的基本工作單元,它是一個 XML 文件,包含了項目的基本信息,用於描述項目如何構建,聲明項目依賴等等。執行任務或目標時,Maven 會在當前目錄中查找並讀取 POM,獲取所需的配置信息,然後執行目標。 1、基本 ...
  • # HttpServletResponse對象 ## 基本介紹 ​ Web伺服器收到客戶端的http請求,會針對每次請求,分別創建一個用於**代表請求**的 request對象 和**代表響應**的 response對象。 ​ request 和 response對象 代表請求和響應:**獲取客戶瑞 ...
  • 不知不覺,《C++面試八股文》已經更新30篇了,這是我第一次寫技術博客,由於個人能力有限,出現了不少紕漏,在此向各位讀者小伙伴們致歉。 為了不誤導更多的小伙伴,以後會不定期的出勘誤文章,請各位小伙伴留意。 在《[C++面試八股文:C++中,設計一個類要註意哪些東西?](https://zhuanla ...
  • ### 1、背景介紹 前兩天,現場的同事使用開發的程式測試時,發現日誌中報`etcdserver: mvcc: database space exceeded`,導致 etcd 無法連接。很奇怪,我們開發的程式只用到了 etcd 做程式的主備,並沒有往 etcd 中寫入大量的數據,為什麼會造成 et ...
  • # WPF複習知識點記錄 由於近幾年主要在做Web項目,客戶端的項目主要是以維護為主,感覺對於基礎知識的掌握沒有那麼牢靠,趁著這個周末重新複習下WPF的相關知識。 文章內容主要來自大佬劉鐵錳老師的經典著作《深入淺出WPF》。 因為是複習,所以知識內容不會一一記錄,如有需要瞭解更多可以看書中內容。 * ...
  • 本章將和大家分享使用 SignalR 生成實時應用的基礎知識。通過本文您將學習如何:使用ASP.NET Core SignalR + MVC + Vue 2.x + require 最終創建一個正常運行的簡易聊天應用。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...