banner
Chenh

Chenh

實體框架核心

實體框架核心#

1. 導航屬性#

// A |->* B
public class A{
  //...
  public List<B> Bs{get;set;}
}

public class B{
  // ...
  public B B{set;get;}
}

導航屬性可以是任何列表類型。

2. 資料庫上下文 ⚠️#

用來協調 EF 和資料庫的管理類。#

  • 一個 Context 是一個繼承自 DbContext 的非抽象類。這個類的功能首先是公開所有實體的集合,操作這些集合可以在儲存中讀取或保存資料,除此之外 Context 保存資料的模型。在使用前需要正確配置各個實體的模型以及其他必要的配置。
  • Context 的另一個重要的功能就是跟蹤對實體的更改,以便我們在保存更改時,它知道該如何處理。
  • Context 跟蹤的每個實體所處的以下狀態之一:未更改 /added,已修改 /modified,已添加 /added,已刪除 /deleted已分離 /detached。可以將上下文視為沙箱,我們可以在其中對實體集合進行更改,然後通過一次保存操作應用這些更改。
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
        {
        }
				// 實體集 DbSet 對應資料庫表
        // 由於Courses表包含了 下面兩個表,因此,即使不寫下方兩表,EF也會自動包含
        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }
      	// 覆蓋默認行為,設定表名
      	protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

DbContext 鉤子方法#

  • OnConfiguring 方法:當 context 需要配置它的 provider 和 連接字串的時候,該方法會被調用,這為開發者提供了介入的入口。
  • OnModelCreating 方法:在 EF Core 組裝資料模型時會被自動調用,你將會在這個方法裡添加構建模型的代碼。
  • SaveChanges 方法:當我們希望將修改保存到資料庫時進行調用。 返回為受保存操作影響的記錄數。

Database Provider / 資料庫提供程序#

  • EF core 是資料庫無關的。因此不同的資料庫需要為其提供特定介面
  • 引入相關資料庫提供程序包,並在 Context 的 OnConfiguring 中添加對應配置方法(UseSqlServer)

3. 服務註冊#

// Startup.cs
/* Using */
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
/* Start up */
public void ConfigureServices(IServiceCollection services)
{
  services.AddDbContext<SchoolContext>(
    options=>options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
  services.AddControllersWithViews();
  // 開發環境 異常篩選功能
  services.AddDatabaseDeveloperPageExceptionFilter();
}
// appsetting.json
{
  "ConnectionStrings": {
    "DefaultConnection": "資料庫字串"
  },
}

4. 創建資料模型#

  • 創建資料模型需要 實體 Entity 類型。實體類型就是一系列 C# 類,通常與資料庫對應。

  • 配置 實體類型資料庫實體 之間的關係。有兩種方式:”fluent API“” 資料註釋 “

    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    // 配置 對應表名 和 對應資料庫架構名
    [Table("blogs",Schema="dbo")]
    public class Blog{
      [Key]	// 主鍵,通常efcore會自動識別
      public int BlogId{set;get;}
      [MaxLength(400)] // 最大長度
      public string Url{set;get;}
      [NotMapped] // 排除映射
      public DataTime LoadedFromDatabase {set;get;}
      [Column("rating",TypeName="decimal(5,2)")] // 映射列名和類型
      public decimal Rating {set;get;}
    }
    // 使用 fluent API 配置默認架構名 
    protected override void OnModelCreating(ModelBuilder modelBuilder){
      modelBuilder.HasDefaultSchema("dbo");
    }
    

“實體引用 / 引用導航” 配置 一對一#

//一Blog 對應 一個 Customer
class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<BlogImage> BlogImages { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //設定 Blog與BlogImage 一對一關係
        modelBuilder.Entity<Blog>()
            .HasOne(b => b.BlogImage)
            .WithOne(i => i.Blog)
            .HasForeignKey<BlogImage>(b => b.BlogForeignKey);
    }
}
public class Blog
{
    public Blog()
    {
        Posts = new HashSet<Post>();
    }

    public int BlogId { get; set; }
    
    public string Name { get; set; }
    public string Url { get; set; }
    public int? ImageId { get; set; }
    // 引用導航 efcore 可以自動配置 寫法見官方文檔
    public BlogImage BlogImage { get; set; }
    public HashSet<Post> Posts { get; set; }
}
public class BlogImage
{
    public int BlogImageId { get; set; }
    public string ImageUrl { get; set; }
    public string Caption { get; set; }
}

“實體集合 / 集合導航” 配置 :一對多#

//實體集合屬性的配置
class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //配置 Blog 的一對多關係
        modelBuilder.Entity<Blog>()
            .HasMany(b => b.Posts)
            .WithOne(p => p.Blog)
            .HasForeignKey(p => p.BlogId);
    }
}

public class Blog
{
    public Blog()
    {
      	// 因為有 HashSet 導航,構造函數要添加此內容
        Posts = new HashSet<Post>();
    }

    public int BlogId { get; set; }

    public string Name { get; set; }
    public string Url { get; set; }
    public int? ImageId { get; set; }
    public BlogImage BlogImage { get; set; }
  	// 集合導航,表示一對多關係,需要HashSet輔助
    public HashSet<Post> Posts { get; set; }
}

public class Post
{
    [Key]
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

總結#

資料模型的作用除了說明資料結構,還需要說明資料關係。關係也是資料庫的核心。

EF core 支持多種方式描述模型關係,使用註釋是最直觀簡單的方法,特殊的關係還需要使用 FluentAPI 輔助。

但兩者都是在 ModelBuilder 中配置相關行為。

5. 遷移 從模型到資料庫#

遷移是使用 EF core 遷移工具從實體模型創建和管理資料庫。

遷移過程#

  1. 按照上述步驟,構建實體模型和關係,會有一個 的類。通常是你打算的資料庫名稱 + Context,這個類繼承與 DbContext。EF core 正是依據這個文件中的實體模型和關係說明來生成資料庫的。

  2. 打開終端

  3. 首先確定安裝 .NET core CLI 工具

    # 全局安裝 dotnet-ef 工具
    dotnet tool install --global dotnet-ef
    # 更新 
    dotnet tool update --global dotnet-ef
    # 為 項目添加 EFCoreDesign 包
    dotnet add package Microsoft.EntityFrameworkCore.Design # 也可以使用Nuget管理器
    # 檢查 工具安裝情況
    dotnet ef
    

    ⚠️ EF Core 工具參考 (.NET CLI) - EF Core | Microsoft Learn 工具的詳細使用方法見此處

  4. 在工程目錄下打開終端:

    # 創建一個遷移
    dotnet ef migrations add InitialCreate
    ## 該命令會生成一個 migration 文件夾,用來記錄遷移,也可以直接修改其內容。
    # 創建資料庫架構
    dotnet ef database update
    

    ⚠️ 關於驗證問題,可以在連接字串中添加TrustServerCertificate=true;Encrypt=true

  5. 完成。

發展模型#

當你的實體模型發生改變,我們需要重新更新資料庫。

首先必須要明確,遷移工具生成的 migrations/ 系列文件,是遷移記錄,是資料庫結構演進的完整說明。它包含了一份資料庫結構快照,和一份遷移文件。

因此,當模型改變時,我們可以手動更改 migrations/ 文件,並再次執行dotnet ef database update來應用遷移。

為了保留資料庫演進的記錄,我們使用以下流程:

  1. 創建新的遷移

    # 這裡最好使用有意義的遷移命名
    dotnet ef migrations add AddSomeTable
    
  2. 應用遷移 dotnet ef database update

說明#

閱讀 migration/ 文件夾,裡面有一系列帶時間戳和遷移名稱的.cs文件,用來指示每次遷移執行的動作。可以看出,當實體模型改變時,EFcore 會自動比較並進行增量更新,而非重新生成資料庫。

另一個文件是資料庫快照的.cs文件,可以看出裡面是真實的資料庫表實體模型。是上一次更新有的資料庫快照。

兩種文件都是 CSharp 文件,可以加入到版本管理中,從而記錄工程開發過程中的資料庫演進。

6 其他#

排除類型#

若當前 Context 引用了其他 Context 中的類型,會在遷移時出現衝突(引用的對象在當前 Context 並不存在,出現錯誤是正常的),此時要從 Context 中排除該類型。

// 這是引用Context類
// 在模型創建鉤子中,排除不存在的類型。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<IdentityUser>()
        .ToTable("AspNetUsers", t => t.ExcludeFromMigrations());
}

列重命名#

EFCore 無法區分列的重命名或刪除,因此,當你重命名列時,EFcore 會生成 Delete-Create 的操作。這可能會導致資料丟失。需要手動更改。

// EFcore 生成的遷移動作
migrationBuilder.DropColumn(
    name: "Name",
    table: "Customers");

migrationBuilder.AddColumn<string>(
    name: "FullName",
    table: "Customers",
    nullable: true);

// 需要將其修改為
migrationBuilder.RenameColumn(
    name: "Name",
    table: "Customers",
    newName: "FullName");

在遷移中引入 SQL 行為#

假設要將實體中原來的 FirstnameLastName 合併成 FullName 重新存儲,並刪除前兩列。

EFcore 不會自動識別這個行為,需要手動引入 SQL 語句用來遷移。

migrationBuilder.AddColumn<string>(
    name: "FullName",
    table: "Customer",
    nullable: true);

migrationBuilder.Sql(
@"
    UPDATE Customer
    SET FullName = FirstName + ' ' + LastName;
");

migrationBuilder.DropColumn(
    name: "FirstName",
    table: "Customer");

migrationBuilder.DropColumn(
    name: "LastName",
    table: "Customer");

⚠️ 此流程是有順序的。

當要利用舊資料進行遷移時,可以使用 SQL

自定義 SQL 遷移#

可以在不改變模型的情況下,生成一個空遷移,在手動填充 SQL 語句。

SQL 可以操作模型不知道的資料庫對象。

migrationBuilder.Sql(
@"
    EXEC ('CREATE PROCEDURE getFullName
        @LastName nvarchar(50),
        @FirstName nvarchar(50)
    AS
        RETURN @LastName + @FirstName;')");

⚠️ 當某個語句必須是 SQL 批處理中的第一個或唯一一個語句時,請使用 EXEC。 它還可以用來解決幂等遷移腳本中的分析程序錯誤,當表中當前不存在引用的列時,可能會發生此類錯誤。

⚠️ 通常 EFCore 使用事務執行遷移操作。當某些資料庫不支持該操作時,可以向migrationBuilder.Sql中傳入參數suppressTransaction: true 來選擇不使用事務。

7. 遷移管理#

刪除遷移#

使用 dotnet ef migrations remove 來移除 上一個遷移

⚠️ 請勿移除已應用的遷移!

列出遷移#

dotnet ef migrations list 列出所有遷移

重置所有#

直接刪除 Migration 文件夾即可刪除所有遷移記錄,詳情:

管理遷移 - EF Core | Microsoft Learn

應用遷移#

  1. 生成 SQL 腳本,從零開始生成資料庫。這是將工程模型部署到生產環境的最佳實踐。

    dotnet ef migrations script [from] [to]
    

    From to 分別是遷移版本名稱。to 默認是最新。可以使用上述代碼回退。

  2. 自動比較 SQL 腳本。對於將遷移應用在現有資料庫,但並不清楚其當前版本的情況下。efcore 會檢索遷移歷史記錄,自動更新。

    dotnet ef migrations script --idempotent
    
  3. 使用命令行工具升級資料庫,這是開發環境測試環境的最佳實踐

    dotnet ef database update [to]
    
  4. 程序運行時執行遷移,這是本地開發測試時的最佳實踐,可以省去每次啟動都手動遷移的步驟。

    // 在 .net 運行主函數中添加
    var host = CreateHostBuilder(args).Build();
    using(var scope = host.Services.CreateScope()){
      var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
      younameContext.Database.Migrate(); // 這句是關鍵。
    }
    host.Run();
    

    運行時應用遷移 - Microsoft Learn

  5. 創建捆綁包

    捆綁 - Microsoft Learn

團隊管理#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。