JD.Efcpt.Build:构建时 EF Core 脚手架以保持 Database‑First 模型同步
Source: Dev.to
jdefcptbuild:在构建时运行 EF Core Scaffold 以保持 Database‑First 模型同步
在使用 Database‑First 方法时,模型(DbContext 和实体类)需要与数据库结构保持同步。手动运行 dotnet ef dbcontext scaffold 很容易被忘记,导致代码和数据库之间出现不一致。为了解决这个问题,我创建了一个名为 jdefcptbuild 的 .NET CLI 工具,它会在每次项目构建时自动执行 Scaffold,确保模型始终与数据库保持最新。
目录
为什么需要自动 Scaffold
-
防止模型漂移
当数据库结构变化(新增列、修改类型等)而忘记重新 Scaffold 时,运行时会抛出异常或返回错误的数据。 -
提升开发效率
开发者不必记住每次迁移后手动运行 Scaffold,只需专注于业务代码。 -
持续集成友好
在 CI/CD 流水线中,构建步骤会自动生成最新的模型,避免因模型不匹配导致的构建失败。
工具概览
jdefcptbuild 是一个轻量级的 .NET Global Tool,内部实现了以下核心功能:
- 读取项目根目录下的
jdefcptbuild.json配置文件。 - 调用
dotnet ef dbcontext scaffold并将生成的代码放入指定的文件夹。 - 在生成前删除旧的 Scaffold 文件,以防止残留的旧代码。
- 支持自定义 Scaffold 参数(如
--use-database-names、--no-pluralize等)。
安装 jdefcptbuild
dotnet tool install -g jdefcptbuild
注意:如果已经安装过旧版本,请先卸载再重新安装:
dotnet tool uninstall -g jdefcptbuild
dotnet tool install -g jdefcptbuild
在项目中配置
1. 添加配置文件 jdefcptbuild.json
在项目根目录(与 .csproj 同级)创建 jdefcptbuild.json,示例内容如下:
{
"connectionString": "Server=localhost;Database=MyDb;User Id=sa;Password=Your_password123;",
"provider": "Microsoft.EntityFrameworkCore.SqlServer",
"outputDir": "Models/Generated",
"namespace": "MyApp.Models.Generated",
"scaffoldOptions": [
"--use-database-names",
"--no-pluralize"
]
}
- connectionString:数据库连接字符串。建议使用 User Secrets 或 Environment Variables,不要直接写在源码中。
- provider:EF Core 数据库提供程序(如
Microsoft.EntityFrameworkCore.SqlServer、Npgsql.EntityFrameworkCore.PostgreSQL等)。 - outputDir:生成的实体类和
DbContext所放置的相对路径。 - namespace:生成代码的命名空间。
- scaffoldOptions:传递给
dotnet ef dbcontext scaffold的额外参数。
2. 修改 .csproj 以在构建时调用工具
在项目文件(*.csproj)中加入以下 Target:
<Target Name="RunJdefcptbuild" BeforeTargets="BeforeBuild">
<Exec Command="jdefcptbuild" />
</Target>
这段配置会在每次执行 dotnet build(或在 IDE 中点击 Build)之前运行 jdefcptbuild。
3. 可选:在 CI/CD 中使用
在 GitHub Actions、Azure Pipelines 或 GitLab CI 中,只需确保已安装 .NET SDK,然后添加一步执行 jdefcptbuild,例如:
- name: Install jdefcptbuild
run: dotnet tool install -g jdefcptbuild
- name: Scaffold EF Core models
run: jdefcptbuild
常见问题
| 问题 | 解决方案 |
|---|---|
| 生成的文件被 Git 追踪 | 将 outputDir 加入 .gitignore,只保留一次性提交的 jdefcptbuild.json。 |
| 连接字符串泄露 | 使用 dotnet user-secrets 或 CI 环境变量,将连接字符串写入 jdefcptbuild.json 时使用占位符 ${CONN_STR},工具会在运行时读取对应的环境变量。 |
| Scaffold 速度慢 | 只在需要时运行 Scaffold(例如在 BeforeBuild 改为 AfterCompile),或使用 --no-build 参数加速。 |
| 自定义模板 | 目前 jdefcptbuild 只支持 EF Core 默认模板。若需要自定义代码生成,请考虑使用 EF Core Power Tools 或 T4。 |
结论
jdefcptbuild 为 Database‑First 开发提供了一个“零配置、自动化”的解决方案,使模型始终与数据库保持同步,避免了手动 Scaffold 带来的错误和额外工作量。通过在构建阶段自动执行 Scaffold,团队可以更专注于业务逻辑,而不必担心模型漂移的问题。
如果你对该工具有改进建议或想贡献代码,欢迎访问项目的 GitHub 仓库并提交 Issue 或 Pull Request。祝开发愉快!
本文由原作者 Jerrett Davis 撰写,翻译仅供学习交流使用。
介绍
如果你在 EF Core 首次发布时使用 Entity Framework,你可能还记得当你去寻找 database‑first 支持时,只找到了…… nothing。
EF Core 作为 code‑first 框架推出。EF6 开发者依赖的 Reverse Engineer 工具(右键点击、指向数据库、生成模型的工作流)并不存在。Microsoft 的立场基本上是 “migrations are the future, figure it out.”
如果你的团队已有现有数据库、拥有真正负责模式的 DBA,或有合规要求规定数据库是唯一可信的来源…… 那么,祝你好运。
社区的反应立刻且强烈。“Where did database first go?” 成为 GitHub issues、Stack Overflow 提问以及那些只想直接操作数据库而不想手写上百个实体类的开发者们的共同抱怨。
(临时)解决方案
随着工具的跟进,EF Core Power Tools 作为社区的答案出现:一个 Visual Studio 扩展,重新带回了逆向工程工作流。
- 指向数据库或 DACPAC。
- 配置一些选项。
- 生成模型。
问题解决了…… 大部分。
Source: …
手动流程问题
手动流程本来可以正常工作——直到它们不再正常。
我在包含旧版数据层的代码库中花了足够的时间,已经认出一种模式。大致如下:
-
初始设置 – 有人安装了 EF Core Power Tools,生成了初始模型,提交了一切,并记录了过程:
“当模式更改时,使用此工具并使用这些设置重新生成模型。”
说得很清楚。 -
时间流逝 – 最初设置的开发者离职。文档变得陈旧。
- 有人用稍有不同的设置重新生成并提交了结果。
- 另有人在模式更改后忘记重新生成。
-
漂移 – 模型漂移,配置漂移,没人再确定“正确”的重新生成流程是什么。大家就……不再一致地执行它。
这并不是一次戏剧性的失败;而是缓慢的侵蚀。这种问题往往在你调试生产问题时才会显现,你会发现实体类缺少了已经在数据库中存在了六个月的列。
如果你在同一个代码库工作足够久,可能已经见过这种情况的某个版本。也许是你发现了漂移,也许是你造成的(不作评判——我们都经历过)。
令人沮丧的是,解决办法总是相同的:
- 重新生成模型。
- 提交更改。
- 提醒所有人在模式更改后重新生成。
……六个月后,你又会再次进行同样的讨论。
失败模式(出了什么问题)
| Problem | Description |
|---|---|
| 所有权 | 在模式更改后,谁负责重新生成?模式作者?数据层所有者?技术负责人?没有明确答案 → 有时所有人都做(混乱),有时没人做(漂移)。 |
| 配置 | EF Core Power Tools 将设置存储在 JSON 文件中(命名空间、可空引用类型、导航属性生成、重命名规则等)。数十个选项意味着不同开发者可能从同一数据库生成不一致的输出。 |
| 工具 | 重新生成需要安装了扩展的 Visual Studio。CI 服务器没有 VS。新开发者可能没有该扩展。远程开发环境可能不支持。一个机器上可行的过程不一定在另一台机器上有效。 |
| 噪声 | 重新生成常会产生大量差异:属性重新排序、空白变化、属性添加——这些并非真实的模式更改,却使提交变得杂乱。开发者逐渐不信任这些差异,变得不愿意运行,问题进一步恶化。 |
| 时机 | 即使所有人都了解流程,也没有强制执行。如果代码引用了模型中不存在的列,只要该路径未被执行,仍然可以编译。错误会在以后才显现,此时已忘记最初的模式更改。 |
这些问题单独来看并不致命,但它们共同导致一个在理论上可行、在实践中失败的流程。
Source: …
更好的想法:将生成作为构建的一部分
如果模型生成可以从命令行调用(可以,通过 EF Core Power Tools CLI),那么它就可以成为构建过程的一部分。
- 不是需要记住去运行的单独步骤。
- 不是所有权不明确的手动过程。
- 只是在运行
dotnet build时自动执行的操作。
构建已经会:
- 还原包。
- 运行分析器。
- 生成制品。
将“从模式生成 EF Core 模型”加入该列表,在概念上并不不同于其他构建时的代码生成。
- 所有权消失——构建拥有它。
- 配置漂移消失——构建使用单一且一致的配置。
- 工具链问题消失——每台机器运行相同的 MSBuild 目标;无需特殊扩展。
介绍 JD.Efcpt.Build
JD.Efcpt.Build 是一个 MSBuild 集成,可以实现 EF Core 模型的自动生成。
工作原理
- 查找模式源 – 可以是编译为 DACPAC 的 SQL Server 数据库项目(
.sqlproj),也可以是指向实时数据库的连接字符串。 - 计算指纹 – 对所有输入(DACPAC 或模式元数据、配置文件、重命名规则、任何自定义模板)进行哈希运算。该指纹代表“当前所有内容的状态”。
- 检测变化 – 如果指纹与上一次构建的不同,则目标会执行生成步骤;否则跳过。
- 生成模型 – 使用存储的配置调用 EF Core Power Tools CLI,在指定文件夹中生成实体类。
- 将生成的文件加入编译 – 生成的
.cs文件会自动包含在项目中,随其他代码一起编译。
好处
| 好处 | 说明 |
|---|---|
| 确定性输出 | 相同的输入 → 每台机器上生成相同的代码。 |
| 零手动步骤 | 无需记得运行 VS 扩展;构建过程自动完成。 |
| CI 友好 | 可在无头代理上运行;不需要 Visual Studio。 |
| 明确所有权 | 构建负责重新生成;开发者只需提交生成的文件。 |
| 降低噪音 | 只有在指纹变化时才会生成,差异仅限于实际的模式更改。 |
| 可配置 | 所有 Power‑Tools 选项均通过一个位于源码控制中的简单 JSON 文件暴露。 |
入门指南
# 将包添加到你的项目
dotnet add package JD.Efcpt.Build
在项目根目录创建一个 efcpt.json(或任意你喜欢的名称):
{
"ConnectionString": "Server=.;Database=MyDb;Trusted_Connection=True;",
"OutputDirectory": "GeneratedModels",
"Namespace": "MyApp.Data.Models",
"UseNullableReferenceTypes": true,
"GenerateDataAnnotations": true,
"RenamingRules": {
"Tables": { "tbl_": "" },
"Columns": { "col_": "" }
}
}
该包会自动添加一个在构建期间运行的 MSBuild 目标。你可以通过设置 EfcptEnabled 属性来按配置禁用它(例如,仅在 CI 中禁用)。
Summary
- Database‑first support disappeared 在早期 EF Core 发行版中消失,导致社区沮丧。
- EF Core Power Tools 填补了空白,但手动重新生成引入了所有权、配置、工具、噪声和时序问题。
- Embedding generation in the build 消除了这些问题。
JD.Efcpt.Build提供了轻量级、确定性、CI‑friendly 解决方案,使 EF Core 模型生成成为构建流水线的一等公民。
试一试,让构建保持你的模型与模式同步——一次搞定。
概述
该包在构建期间自动化 EF Core 模型生成。它:
- 检测更改:通过比较输入的指纹(使用 XxHash64)来实现。
- 跳过生成:当指纹与上一次运行匹配时,跳过生成——对增量构建没有任何开销。
- 运行生成:仅在输入实际更改时才执行生成。
它的作用
- 生成模型:使用 EF Core Power Tools CLI(与手动运行的命令相同)。
- 输出:
obj/efcpt/Generated/,文件扩展名为.g.cs。
- 输出:
- 自动将生成的文件添加到编译:无需编辑项目文件或管理包含项。
模式
不同的团队以不同方式管理数据库模式,因此该包支持两种模式。
DACPAC 模式
- 适用于使用 SQL Server Database Projects(
.sqlproj)的团队。 - 项目在受版本控制的 SQL 文件中定义模式。
- 包会构建项目,生成 DACPAC,并从中生成模型。
..\Database\MyDatabase.sqlproj
优势
- 模式位于源代码控制中。
- 更改通过拉取请求(pull request)进行。
- DACPAC 是确定性的构建产物。
连接字符串模式
- 适用于 没有 数据库项目的团队。
- 当你从开发数据库、云数据库进行脚手架,或仅仅不想使用 DACPAC 时非常有用。
$(DB_CONNECTION_STRING)
- 包会连接数据库,查询系统表,并根据模式元数据生成模型。
- 指纹是基于该元数据计算的,因此增量构建仍然有效。
两种模式共享相同的配置文件并产生相同的输出;它们唯一的区别在于 模式的来源。
最小设置
添加包引用:
<PackageReference Include="JD.Efcpt.Build" Version="x.y.z" />
如果你的解决方案中有 .sqlproj 并且 项目目录中有 efcpt-config.json,这就足够了。运行 dotnet build,模型就会出现。
配置 (efcpt-config.json)
{
"names": {
"root-namespace": "MyApp.Data",
"dbcontext-name": "ApplicationDbContext"
},
"code-generation": {
"use-nullable-reference-types": true,
"enable-on-configuring": false
}
}
enable-on-configuring: false→ 生成的DbContext不 包含硬编码的连接字符串;您需要在 DI 容器中进行配置。
重命名规则
如果您的数据库命名约定无法直接映射到 C#,请添加重命名规则:
[
{
"SchemaName": "dbo",
"Tables": [
{
"Name": "tbl_Users",
"NewName": "User",
"Columns": [
{ "Name": "user_id", "NewName": "Id" }
]
}
]
}
]
结果:tbl_Users.user_id → User.Id。数据库保持其约定,您的 C# 代码遵循自己的约定,且映射受版本控制。
部分类 – 保持自定义逻辑安全
Generated entity (auto‑generated, regenerated each build):
// obj/efcpt/Generated/User.g.cs
public partial class User
{
public int Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Your custom logic (never overwritten):
// Models/User.cs
public partial class User
{
public string FullName => $"{FirstName} {LastName}";
public bool HasValidEmail => Email?.Contains("@") ?? false;
}
两个部分会编译成一个完整的类。这种分离方式比在同一个文件中混合生成代码和手写代码更为整洁。
CI/CD – Zero‑Touch Integration
手动重新生成在流水线中效果不佳。使用构建时生成,CI 只需构建:
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Build
run: dotnet build --configuration Release
env:
DB_CONNECTION_STRING: ${{ secrets.DB_CONNECTION_STRING }}
- 无需额外的 EF Core 生成步骤。
- 在 .NET 10+ 上,包使用
dotnet dnx直接从包源执行工具(无需全局工具安装)。 - 在旧版本中则回退到工具清单或全局工具。
修改模式的 Pull Request 会自动生成相应的模型更改,保持模式与代码同步。
调试与诊断
详细日志
<LogLevel>detailed</LogLevel>
构建输出 现在会显示找到的输入、计算得到的指纹,以及生成是已运行还是被跳过。
检查已解析的输入
构建完成后,打开 obj/efcpt/resolved-inputs.json。它精确列出了该包在每种模式下视为输入的内容。
检查指纹
obj/efcpt/fingerprint.txt 包含当前的指纹。将其与之前的运行结果进行比较,以了解生成为何运行(或未运行)。
最后说明
该包的设计旨在实用:快速的指纹检查保持增量构建成本低廉,生成的代码位于可预测的位置(obj/efcpt/Generated)。使用部分类来编写自定义逻辑,配置符合工作流的模式,让构建系统处理其余所有事务。
适用对象
- 数据库优先开发,当你遇到 重新生成协调 问题时。
- 项目经常更改模式(例如,每周发布),并发现手动重新生成是一个摩擦来源。
- 团队需要构建在 所有环境中保持一致——本地机器、CI 服务器、新开发者的笔记本——以便每位开发者从相同的输入获得相同的生成代码。
谁可能不适用
- 基本上是 静态 的模式;很少的更改使手动重新生成可以接受。
- 将 Code‑first 迁移视为唯一真相——属于不同的问题领域。
- 不使用 EF Core Power Tools 的项目;此包自动化该工具,因此其他生成方式不适用。