Scaffolding (反向工程)

反向工程是以資料庫架構為基礎的 Scaffolding 實體類型類別和類別 DbContext 的程式。 您可以使用 EF Core 套件管理員主控台 (PMC) 工具的 Scaffold-DbContext 命令,或 .NET 命令列介面 (CLI) 工具的 dotnet ef dbcontext scaffold 命令來執行還原工程。

注意

這裡記載的 DbContext 和 實體類型的 Scaffold 與使用 Visual Studio ASP.NET Core 中控制器的 Scaffold 不同,此處並未記載。

提示

如果您使用 Visual Studio,請嘗試 EF Core Power Tools 社群延伸模組。 這些工具提供圖形化工具,其建置在 EF Core 命令行工具之上,並提供額外的工作流程和自定義選項。

必要條件

  • 在 Scaffolding 之前,您必須安裝 PMC 工具,其僅適用於 Visual Studio,或 .NET CLI 工具,這些工具橫跨 .NET 支援的所有平臺。
  • 在您要 Scaffold 至的專案中安裝 Microsoft.EntityFrameworkCore.Design 適用的 NuGet 套件。
  • 安裝以您要從中 Scaffold 之資料庫架構為目標之資料庫提供者的 NuGet 套件

必要引數

PMC 和 .NET CLI 命令都有兩個必要自變數:資料庫 連接字串,以及要使用的 EF Core 資料庫提供者。

連接字串

命令的第一個引數是資料庫的連接字串。 這些工具會使用此連接字串來讀取資料庫結構描述。

您引用和逸出 連接字串 的方式取決於您用來執行命令的殼層;如需詳細資訊,請參閱殼層的檔。 例如,PowerShell 會要求您逸出 $ 字元,而非 \

下列範例會從Chinook位於電腦 SQL Server LocalDB 實體例的資料庫,使用資料庫提供者,Microsoft.EntityFrameworkCore.SqlServer建立實體類型和 DbContext

dotnet ef dbcontext scaffold "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Chinook" Microsoft.EntityFrameworkCore.SqlServer

連接字串的使用者秘密

如果您具備使用裝載模型和組態系統的 .NET 應用程式 (例如 ASP.NET Core 專案) 則可以使用 Name=<connection-string> 語法從組態讀取連接字串。

例如,請考慮使用下列組態檔的 ASP.NET Core 應用程式:

{
  "ConnectionStrings": {
    "Chinook": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=Chinook"
  }
}

此組態檔中的 連接字串 可用來使用下列項目從資料庫建立 Scaffold:

dotnet ef dbcontext scaffold "Name=ConnectionStrings:Chinook" Microsoft.EntityFrameworkCore.SqlServer

不過,將 連接字串 儲存在組態檔中並不是個好主意,因為透過推送至原始檔控制來意外公開它們太容易了。 相反地,連接字串 應該以安全的方式儲存,例如使用 Azure 金鑰保存庫,或在本機工作時,秘密管理員工具也稱為「用戶密碼」。

例如,若要使用用戶密碼,請先從您的 ASP.NET Core 組態檔中移除 連接字串。 接下來,在與 ASP.NET Core 專案相同的目錄中執行下列命令,以初始化用戶密碼:

dotnet user-secrets init

此命令會在您的電腦上設定與原始程式碼分開的記憶體,並將此記憶體的金鑰新增至專案。

接下來,將 連接字串 儲存在用戶密碼中。 例如:

dotnet user-secrets set ConnectionStrings:Chinook "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Chinook"

現在,先前從組態檔使用具名 連接字串的相同命令會改用儲存在用戶密碼中的 連接字串。 例如:

dotnet ef dbcontext scaffold "Name=ConnectionStrings:Chinook" Microsoft.EntityFrameworkCore.SqlServer

scaffolded 程式代碼中的 連線 字串

根據預設,Scaffolder 會在 Scaffold 程式代碼中包含 連接字串,但出現警告。 例如:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
    => optionsBuilder.UseSqlServer("Data Source=(LocalDb)\\MSSQLLocalDB;Database=AllTogetherNow");

如此一來,產生的程式代碼就不會在第一次使用時當機,這將會是非常糟糕的學習體驗。 不過,如警告所示,連接字串 不應該存在於生產程序代碼中。 如需可管理 連接字串的各種方式,請參閱 DbContext 存留期、設定和初始化

提示

-NoOnConfiguring 可以傳遞 (Visual Studio PMC) 或 --no-onconfiguring (.NET CLI) 選項,以抑制建立OnConfiguring包含 連接字串 的方法。

提供者名稱

第二個引數是提供者名稱。 提供者名稱通常會與提供者的 NuGet 套件名稱相同。 例如,針對 SQL Server 或 Azure SQL,請使用 Microsoft.EntityFrameworkCore.SqlServer

命令列選項

Scaffolding 程式可由各種命令行選項控制。

指定數據表和檢視

根據預設,資料庫架構中的所有數據表和檢視都會建構成實體類型。 您可以藉由指定架構和數據表來限制要建立的數據表和檢視表。

-Schemas (Visual Studio PMC) 或 --schema (.NET CLI) 自變數會指定將產生實體類型的數據表和檢視架構。 如果省略此自變數,則會包含所有架構。 如果使用此選項,則即使未使用 或 --table明確包含架構中的所有數據表和檢視,架構中的所有數據表和檢視也會包含在-Tables模型中。

-Tables (Visual Studio PMC) 或 --table (.NET CLI) 自變數會指定將產生實體類型的數據表和檢視。 特定架構中的數據表或檢視表可以使用 'schema.table' 或 'schema.view' 格式來包含。 如果省略此選項,則會包含所有數據表和檢視表。 |

例如,若要只 Artists 建立 和 Albums 數據表的 Scaffold:

dotnet ef dbcontext scaffold ... --table Artist --table Album

若要從和 Contractor 架構建立所有資料表和檢視Customer表:

dotnet ef dbcontext scaffold ... --schema Customer --schema Contractor

例如,若要從架構建立Purchases數據表,以及Accounts架構中的 ContractorContracts 數據表:Customer

dotnet ef dbcontext scaffold ... --table Customer.Purchases --table Contractor.Accounts --table Contractor.Contracts

保留資料庫名稱

資料表和資料行名稱預設會進行修正,以更符合類型和屬性的 .NET 命名慣例。 -UseDatabaseNames 指定 (Visual Studio PMC) 或 --use-database-names (.NET CLI) 將會停用此行為,盡可能保留原始資料庫名稱。 此外,無效的 .NET 識別碼仍會進行修正,而導覽屬性等合成名稱仍會符合 .NET 命名慣例。

例如,請考慮下列數據表:

CREATE TABLE [BLOGS] (
    [ID] int NOT NULL IDENTITY,
    [Blog_Name] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Blogs] PRIMARY KEY ([ID]));

CREATE TABLE [posts] (
    [id] int NOT NULL IDENTITY,
    [postTitle] nvarchar(max) NOT NULL,
    [post content] nvarchar(max) NOT NULL,
    [1 PublishedON] datetime2 NOT NULL,
    [2 DeletedON] datetime2 NULL,
    [BlogID] int NOT NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([id]),
    CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogID]) REFERENCES [Blogs] ([ID]) ON DELETE CASCADE);

根據預設,下列實體類型會從這些數據表進行 Scaffold:

public partial class Blog
{
    public int Id { get; set; }
    public string BlogName { get; set; } = null!;
    public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}

public partial class Post
{
    public int Id { get; set; }
    public string PostTitle { get; set; } = null!;
    public string PostContent { get; set; } = null!;
    public DateTime _1PublishedOn { get; set; }
    public DateTime? _2DeletedOn { get; set; }
    public int BlogId { get; set; }
    public virtual Blog Blog { get; set; } = null!;
    public virtual ICollection<Tag> Tags { get; set; } = new List<Tag>();
}

不過,使用 -UseDatabaseNames--use-database-names 會產生下列實體類型:

public partial class BLOG
{
    public int ID { get; set; }
    public string Blog_Name { get; set; } = null!;
    public virtual ICollection<post> posts { get; set; } = new List<post>();
}

public partial class post
{
    public int id { get; set; }
    public string postTitle { get; set; } = null!;
    public string post_content { get; set; } = null!;
    public DateTime _1_PublishedON { get; set; }
    public DateTime? _2_DeletedON { get; set; }
    public int BlogID { get; set; }
    public virtual BLOG Blog { get; set; } = null!;
}

使用對應屬性 (也稱為資料批註)

根據預設, ModelBuilder 實體類型會使用 中的 OnModelCreating API 進行設定。 指定 -DataAnnotations (PMC) 或 --data-annotations (.NET Core CLI) 以盡可能改用 對應屬性

例如,使用 Fluent API 會 Scaffold 下列內容:

entity.Property(e => e.Title)
    .IsRequired()
    .HasMaxLength(160);

而使用資料註解則會 Scaffold 下列內容:

[Required]
[StringLength(160)]
public string Title { get; set; }

提示

無法使用對應屬性來設定模型的某些層面。 Scaffolder 仍會使用模型建置 API 來處理這些案例。

DbCoNtext 名稱

Scaffolded DbContext 類別名稱預設會是以 Context 後綴的資料庫名稱。 若要指定不同的名稱,請在 PMC 中使用 -Context,以及在 .NET Core CLI 中使用 --context

目標目錄和命名空間

實體類別和 DbCoNtext 類別會 Scaffold 到專案的根目錄中,並使用專案的預設命名空間。

您可以使用 --output-dir 指定類別的 Scaffold 位置所在目錄,而 --context-dir 可用來將 DbCoNtext 類別 Scaffold 到與實體類型類別不同的目錄:

dotnet ef dbcontext scaffold ... --context-dir Data --output-dir Models

命名空間預設會是根命名空間加上專案根目錄下任何子目錄的名稱。 不過,您可以使用 來覆寫所有輸出類別的 --namespace命名空間。 您也可以使用 --context-namespace,只針對 DbCoNtext 類別覆寫命名空間:

dotnet ef dbcontext scaffold ... --namespace Your.Namespace --context-namespace Your.DbContext.Namespace

Scaffold 程式代碼

從現有資料庫進行 Scaffolding 的結果為:

  • 檔案,其中包含繼承自的類別 DbContext
  • 每個實體類型的檔案

提示

從 EF7 開始,您也可以使用 T4 文字範本來自訂產生的程式碼。 如需詳細資訊,請參閱自訂還原工程範本

C# 可為 Null 的參考型別

Scaffolder 可以建立 EF 模型和實體類型,以使用 C# 可為 Null 的參考型 別 (NRT)。 在 C# 專案中啟用 NRT 支援時,NRT 使用方式會自動進行 Scaffold 處理。

例如,下表 Tags 同時包含可為 Null 的不可為 Null 字串資料列:

CREATE TABLE [Tags] (
  [Id] int NOT NULL IDENTITY,
  [Name] nvarchar(max) NOT NULL,
  [Description] nvarchar(max) NULL,
  CONSTRAINT [PK_Tags] PRIMARY KEY ([Id]));

這會導致產生的類別中對應的可為 Null 和不可為 Null 的字串屬性:

public partial class Tag
{
    public Tag()
    {
        Posts = new HashSet<Post>();
    }

    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public string? Description { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

同樣地,下表 Posts 包含數據表的必要關聯性 Blogs

CREATE TABLE [Posts] (
    [Id] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NOT NULL,
    [Contents] nvarchar(max) NOT NULL,
    [PostedOn] datetime2 NOT NULL,
    [UpdatedOn] datetime2 NULL,
    [BlogId] int NOT NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([Id]));

這會導致部落格之間不可為 Null 的 (必要) 關聯性 Scaffold:

public partial class Blog
{
    public Blog()
    {
        Posts = new HashSet<Post>();
    }

    public int Id { get; set; }
    public string Name { get; set; } = null!;

    public virtual ICollection<Post> Posts { get; set; }
}

文章:

public partial class Post
{
    public Post()
    {
        Tags = new HashSet<Tag>();
    }

    public int Id { get; set; }
    public string Title { get; set; } = null!;
    public string Contents { get; set; } = null!;
    public DateTime PostedOn { get; set; }
    public DateTime? UpdatedOn { get; set; }
    public int BlogId { get; set; }

    public virtual Blog Blog { get; set; } = null!;

    public virtual ICollection<Tag> Tags { get; set; }
}

多對多關聯性

Scaffolding 程式會偵測簡單的聯結數據表,並自動為其產生 多對多對應 。 例如,請考慮 和TagsPosts數據表,以及連接它們的聯結數據表PostTag

CREATE TABLE [Tags] (
  [Id] int NOT NULL IDENTITY,
  [Name] nvarchar(max) NOT NULL,
  [Description] nvarchar(max) NULL,
  CONSTRAINT [PK_Tags] PRIMARY KEY ([Id]));

CREATE TABLE [Posts] (
    [Id] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NOT NULL,
    [Contents] nvarchar(max) NOT NULL,
    [PostedOn] datetime2 NOT NULL,
    [UpdatedOn] datetime2 NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]));

CREATE TABLE [PostTag] (
    [PostsId] int NOT NULL,
    [TagsId] int NOT NULL,
    CONSTRAINT [PK_PostTag] PRIMARY KEY ([PostsId], [TagsId]),
    CONSTRAINT [FK_PostTag_Posts_TagsId] FOREIGN KEY ([TagsId]) REFERENCES [Tags] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_PostTag_Tags_PostsId] FOREIGN KEY ([PostsId]) REFERENCES [Posts] ([Id]) ON DELETE CASCADE);

Scaffolded 時,這會產生 Post 的類別:

public partial class Post
{
    public Post()
    {
        Tags = new HashSet<Tag>();
    }

    public int Id { get; set; }
    public string Title { get; set; } = null!;
    public string Contents { get; set; } = null!;
    public DateTime PostedOn { get; set; }
    public DateTime? UpdatedOn { get; set; }
    public int BlogId { get; set; }

    public virtual Blog Blog { get; set; } = null!;

    public virtual ICollection<Tag> Tags { get; set; }
}

以及 Tag 的類別:

public partial class Tag
{
    public Tag()
    {
        Posts = new HashSet<Post>();
    }

    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public string? Description { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

但是數據表沒有類別 PostTag 。 相反地,會建立多對多關聯性的組態:

entity.HasMany(d => d.Tags)
    .WithMany(p => p.Posts)
    .UsingEntity<Dictionary<string, object>>(
        "PostTag",
        l => l.HasOne<Tag>().WithMany().HasForeignKey("PostsId"),
        r => r.HasOne<Post>().WithMany().HasForeignKey("TagsId"),
        j =>
            {
                j.HasKey("PostsId", "TagsId");
                j.ToTable("PostTag");
                j.HasIndex(new[] { "TagsId" }, "IX_PostTag_TagsId");
            });

其他程式設計語言

Microsoft Scaffold C# 程式代碼所發佈的 EF Core 套件。 不過,基礎 Scaffolding 系統支援外掛程式模型來建構成其他語言。 此外掛程式模型由各種社群執行專案使用,例如:

自訂程序代碼

從EF7開始,自定義所產生程序代碼的最佳方式之一是 自定義用來產生它的 T4 範本。

程式代碼在產生之後也可以變更,但執行此動作的最佳方式取決於您是否打算在資料庫模型變更時重新執行 Scaffolding 程式。

僅 Scaffold 一次

使用此方法,Scaffold 程式代碼會提供程式碼型對應的起點。 您可以視需要對產生的程式代碼進行任何變更,使其變成一般程式代碼,就像專案中的任何其他程式碼一樣。

您可以透過下列兩種方式之一,讓資料庫和 EF 模型保持同步:

  • 切換至使用 EF Core 資料庫移轉,並使用實體類型和 EF 模型組態作為事實來源,使用移轉來驅動架構。
  • 當資料庫變更時,手動更新實體類型和 EF 組態。 例如,如果新的數據行加入至數據表,則將數據行的屬性新增至對應的實體類型,並使用 中的 OnModelCreating對應屬性和/或程式代碼新增任何必要的組態。 這相當容易,唯一真正的挑戰是一個程式,以確保以某種方式記錄或偵測資料庫變更,讓負責程式代碼的開發人員可以做出反應。

重複的 Scaffolding

一次 Scaffolding 的替代方法是每次資料庫變更時重新建立 Scaffold。 這會覆寫任何先前建構的程序代碼,這表示對實體類型或 EF 組態所做的任何變更都會遺失。

[TIP]根據預設,EF 命令不會覆寫任何現有的程序代碼,以防止意外遺失程序代碼。 -Force (Visual Studio PMC) 或 --force (.NET CLI) 自變數可用來強制覆寫現有的檔案。

由於會覆寫 Scaffold 程式代碼,因此最好不要直接修改它,而是依賴 部分類別和方法,以及 EF Core 中允許覆寫組態的機制。 具體而言:

  • 類別 DbContext 和實體類別都會產生為部分。 這允許在個別的檔案中引進其他成員和程序代碼,在 Scaffolding 執行時不會覆寫。
  • 類別 DbContext 包含稱為 OnModelCreatingPartial的部分方法。 這個方法的實作可以加入 至 的部分類別。DbContext 接著會在呼叫 之後 OnModelCreating 呼叫它。
  • 使用 API 建立的 ModelBuilder 模型組態會覆寫慣例或對應屬性所完成的任何組態,以及模型產生器上先前完成的設定。 這表示 中的 OnModelCreatingPartial 程式代碼可用來覆寫 Scaffolding 程式所產生的組態,而不需要移除該組態。

最後,請記住,從EF7開始,您可以自定義用來產生程式碼的 T4 範本。 這通常比使用預設值的 Scaffolding 更有效率的方法,然後使用部分類別和/或方法進行修改。

運作方式

還原工程會從讀取資料庫結構描述開始。 其會讀取資料表、資料行、條件約束和索引的相關資訊。

接著,還原工程會使用結構描述的資訊來建立 EF Core 模型。 資料表會用於建立實體類型;資料行用於建立屬性;而外部索引鍵或用於建立關聯性。

最後,模型會用於產生程式碼。 為了重新建立與您應用程式相同的模型,會 Scaffold 對應的實體類型類別、Fluent API 和資料註解。

限制

  • 模型的相關項目無法全部都使用資料庫結構描述來表示。 例如,繼承階層擁有的類型資料表分割的相關資訊並不會出現在資料庫結構描述中。 因此,這些建構永遠不會進行 Scaffold。
  • 此外,EF Core 提供者可能不支援部分資料行類型。 這些資料行不會包含在模型中。
  • 您可以在EF Core 模型中定義 並行令牌 ,以防止兩位用戶同時更新相同的實體。 有些資料庫有特殊類型來代表這種類型的數據行(例如 SQL Server 中的 rowversion),在此情況下,我們可以反轉工程師這項資訊;不過,不會建立其他並行令牌。