EF Core 模型提取实战指南:从 DbContext 与迁移到 D2 数据库关系图
【免费下载链接】awesome-copilotCommunity-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copilot
导读
本文讲解如何从任意 Entity Framework Core(EF Core)代码库中系统化地提取数据库模型——包括表、列、主外键、索引、拥有类型与多对多联接表——并据此生成可渲染为 SVG/PNG 的 D2 实体关系图。文中方法源自本仓库efcore-d2-db-diagram技能中的模型提取规范(见 references/efcore-model-extraction.md),读完你既能掌握一套可复用的「源码检查 → 模型归并 → 图谱产出」流程,也能准确判断 EF Core 各配置来源发生冲突时该以谁为准。
为什么需要「模型提取」这一步
EF Core 的持久化模型并不等于 C# 类的原始形状:表名可能被ToTable改写,外键可能是未在实体中声明的影子属性,多对多关系可能由约定隐式生成联接表,值对象可能通过OwnsOne/OwnsMany以独立表或内联列的形式持久化。因此,直接照着实体类的字段画图,几乎必然与实际数据库结构产生偏差。
模型提取的目的正是建立一个「规范化后的数据库模型(normalized database model)」:以数据库表为第一等公民,明确每张表的真实名称、主键、外键、必填/可选列、索引、约束与删除行为,之后再进入 D2 生成阶段。这也是 SKILL.md 中工作流第 7 步「Build a normalized database model before writing D2」所强调的关键中间产物。
检查顺序:从哪里开始读代码
提取模型时,应按下述顺序逐层检查源码。顺序本身是有意义的——越靠前的文件越能反映最终持久化形态:
DbContext类:确定实体集与配置入口。通常一个DbContext对应一个数据库或一个限界上下文,是整张图的根。DbSet<T>声明:列出所有可被查询的实体集合,是「哪些类型持久化为表」的第一线索。OnModelCreating:Fluent API 的主战场,集中定义表名、键、索引、关系与删除行为。IEntityTypeConfiguration<T>类:按实体拆分的配置类(常配合ApplyConfigurationsFromAssembly使用),是 Fluent API 的结构化组织形式。- 实体类(Entity classes):确认属性、导航属性与数据注解。
- 迁移与模型快照(Migrations & model snapshot):验证最终落库的实际表结构。
- 数据注解(Data annotations):
[Key]、[Required]、[MaxLength]、[Column]、[Table]等特性标注的声明式约束。
这一顺序与本仓库中 SKILL.md 推荐工作流的前 6 步一致:先定位DbContext与DbSet<T>,再读实体与配置类,最后用迁移核验。
映射优先级:来源冲突时以谁为准
不同配置来源可能相互矛盾——例如实体上用[Required]标注了必填,但 Fluent API 里IsRequired(false)覆盖了它;又例如迁移快照中的表名与实体类命名不一致。此时必须按下述优先级裁决,而不是凭直觉:
- 最新迁移 / 模型快照:这是数据库的「既成事实」,代表已经应用(或将要应用)的真实结构,优先级最高。
- Fluent API(
OnModelCreating与IEntityTypeConfiguration<T>):程序化配置,优先级次之。 - 数据注解:声明式配置,再次之。
- EF Core 约定(Conventions):在没有任何显式配置时才生效的默认行为。
- C# 类形状(class shape):仅作为兜底参考,例如类型推断、属性命名映射。
这条优先级在 SKILL.md 的「Source Priority」一节中被原样强调:当来源不一致时,始终以最新迁移/快照为准,其次是 Fluent API,再次是数据注解、约定,最后才是 C# 原始形态。
需要识别的关键 EF Core API
提取时应在全库范围内检索下列 API,它们各自对应数据库模型中的一类要素:
| API | 提取的信息 |
|---|---|
ToTable | 实际表名;ToTable("Table", "schema")还指定数据库 schema |
HasKey | 主键;多个属性构成复合主键 |
HasAlternateKey | 备用键(唯一约束) |
HasIndex | 索引定义;配合IsUnique得到唯一索引 |
IsUnique | 索引唯一性标志 |
Property | 对某个属性做精细映射(列名、类型、长度、转换等)的入口 |
HasColumnName | 列的实际名称(区别于属性名) |
HasColumnType | 数据库列类型,如varchar(200) |
IsRequired | 列/关系是否必填(非空) |
HasMaxLength | 字符串列最大长度 |
HasConversion | 值转换器(如枚举 ↔ 字符串、复杂对象 ↔ JSON),影响持久化类型 |
HasOne/WithMany/WithOne | 关系两端的导航配置 |
HasForeignKey | 外键属性或影子外键列 |
OnDelete | 删除行为:Cascade/Restrict/NoAction/SetNull/ClientSetNull |
OwnsOne/OwnsMany | 拥有类型(值对象),可内联列或独立表 |
UsingEntity | 显式声明多对多联接表 |
Ignore | 被忽略的属性或实体(不持久化,图上不应出现) |
识别出上述 API 后,还需同步记录:影子属性(仅在 Fluent API 中配置、实体中不存在的属性)、值转换对列类型的影响、枚举属性的持久化形式,以及被Ignore排除的部分——后者在画图时必须主动剔除。
用迁移校验事实:快照里的真实结构
迁移(Migrations)是模型提取的「事实核查层」,因为它是 EF Core 根据模型计算出的、最终将应用到数据库的操作序列。通过迁移文件及其ModelSnapshot,可以确认:
- 实际表名:与
ToTable配置或约定命名核对。 - 联接表(join tables):多对多隐式生成的表,实体类中往往不存在对应类型。
- 影子外键列(shadow FK columns):未声明为实体属性、仅存在于数据库中的外键列。
- 索引:包括唯一索引与复合索引。
- 复合键:由多个列构成的主键或备用键。
- 删除行为:外键约束的
ON DELETE语义。 - 仅存在于迁移中的表(migration-only tables):例如
__EFMigrationsHistory这类由 EF Core 自身维护、与业务实体无关的表。
从迁移中读取这些信息后,再回到实体与 Fluent API 对照验证——这正是「映射优先级」把迁移快照列为最高依据的原因。本仓库的 SKILL.md 也明确规定:迁移数据用于「confirm table names, join tables, indexes and delete behaviors」,并推荐在每次重新生成图之前都重新读取映射与迁移,避免模型过期。
提取结果如何进入 D2 图:关系与分组规则
模型提取的产出要落到.d2文件中,需要配合本技能的其他参考文档完成三件事:
1. 关系推断
参照 references/relationship-rules.md 将提取到的导航与键信息归并为四类关系,统一用「从依赖表指向主表」的有向边表达:
- 一对多(1:N):由
HasOne(...).WithMany(...)、依赖方外键属性、主方集合导航识别,渲染为Orders.ClientId -> Clients.Id: "N:1"。 - 一对一(1:1):由
HasOne(...).WithOne(...)、唯一外键索引或共享主键关系识别,渲染为ClientProfiles.ClientId -> Clients.Id: "1:1"。 - 多对多(N:N):由
UsingEntity、双向集合导航(无显式联接实体)或迁移中带两个外键与复合键的联接表识别,默认显式渲染联接表。 - 拥有类型(owned):由
OwnsOne、OwnsMany、[Owned]识别,默认内联展示,除非检测到表拆分或独立表映射。 - 可选关系:外键可空、配置了
IsRequired(false)或迁移列为可空时判定为可选,图中用虚线边表示。
2. 表与样式表达
参照 references/d2-erd-style.md 使用sql_table形状与统一样式类:
Clients: { shape: sql_table Id: uuid {constraint: primary_key} Name: varchar(200) Status: enum }样式约定(定义于 SKILL.md 的 Style Rules):主实体表实线边框,联接表虚线边框,拥有类型浅色描边或内联字段,技术表弱化(style.opacity: 0.55),迁移专属表点线边框;级联删除在边上加cascade后缀标注。
3. 分组与质量门禁
按 references/grouping-modes.md 提供四种分组模式:bounded-context(按领域/文件夹聚类)、schema(按数据库 schema,如public、auth、billing)、namespace(按 C# 命名空间)、flat(无容器,适合小 schema)。交付前对照 references/quality-gate.md 逐项自检:确认所选DbContext、核对 Fluent API 与迁移中的表名、包含主外键与基数、按用户选择呈现联接表与拥有类型、隐藏的技术表必须在摘要中列出、用d2 fmt校验语法、容器内边使用完整点号路径,并提供渲染命令。
端到端流程小结
将模型提取接入实际生成任务后,完整链路为:
- 读取项目结构,定位全部
DbContext类与DbSet<T>声明。 - 阅读
OnModelCreating与所有IEntityTypeConfiguration<T>,记录 Fluent API 配置。 - 阅读实体类、拥有类型、枚举与值对象,记录数据注解。
- 存在迁移时读取迁移与模型快照,核验表名、联接表、索引、删除行为与影子列。
- 按「迁移 > Fluent API > 数据注解 > 约定 > C# 形状」的优先级归并出规范化数据库模型。
- 回答本技能规定的强制问卷(选择
DbContext、列显示范围、拥有类型与联接表的呈现方式、分组模式、布局引擎等,快速生成时可采用 SKILL.md 中列出的默认值)。 - 生成
.d2源文件,用d2 fmt校验,再用d2 --layout=elk schema.d2 schema.svg渲染输出。
整个过程中,模型提取(efcore-model-extraction.md)是第一步也是决定图质量的一步:它把「读源码」变成了一套有顺序、有优先级、有核验手段的工程化流程,保证最终生成的 D2 图反映的是 EF Core 的真实持久化模型,而非 C# 类的表面形状。
【免费下载链接】awesome-copilotCommunity-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copilot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考