DncZeus实战开源项目(四)数据库设计
// dbContext.cs
......
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DncPermission>(entity =>
{
// 设置权限Code为唯一索引
entity.HasIndex(x => x.Code).IsUnique();
/*
物理层面:Permission (1) ←→ (1) Menu (一对一记录)
业务层面:Menu (1) ←→ (*) Permission (一对多)
*/
entity.HasOne(x => x.Menu).WithMany(x => x.Permissions).HasForeignKey(x => x.MenuGuid);
});
base.OnModelCreating(modelBuilder);
}
表关系的正确理解
1. 物理表关系:一对一
从数据库表层面看,这确实是一对一关系:
DncPermission 表 DncMenu 表
----------------- ------------
PermissionId (PK) MenuId (PK)
MenuGuid (FK) ←→ Id (PK)
每个权限记录只能属于一个菜单(通过外键 MenuGuid),这就是物理表的一对一关系。
2. 业务逻辑关系:一对多
从业务对象层面看,这是一对多关系:
- 一个菜单 可以有多个权限
- 一个权限 只能属于一个菜单
3. 混淆的根源:来自于两个不同层面的视角
错误理解:
- 认为表关系应该是"一对多"
- 但实际上每个权限记录只能关联一个菜单记录
正确理解:
业务层面:Menu (1) ←→ (*) Permission (一对多)
物理层面:Permission (1) ←→ (1) Menu (一对一记录)
4. 具体示例说明
数据示例:
-- DncMenu 表
MenuId MenuName
1 用户管理
2 系统设置
-- DncPermission 表
PermissionId PermissionCode MenuGuid
101 user.view 1
102 user.edit 1
103 system.config 2
关系解释:
- 菜单"用户管理"(ID=1) 有两个权限:查看、编辑
- 每个权限记录只能指向一个菜单记录
- 这就是一对多关系通过外键实现一对一物理连接
5. EF Core 代码映射
// 在 Menu 实体中
public class DncMenu
{
public Guid Id { get; set; }
public string Name { get; set; }
// 一个菜单有多个权限
public ICollection<DncPermission> Permissions { get; set; }
}
// 在 Permission 实体中
public class DncPermission
{
public Guid Id { get; set; }
public string Code { get; set; }
// 一个权限属于一个菜单
public Guid MenuGuid { get; set; }
public DncMenu Menu { get; set; }
}
6. 总结
不会造成混乱的原因:
- 物理层面:每条权限记录只关联一条菜单记录(一对一)
- 逻辑层面:从菜单角度可以找到多个权限(一对多)
- 外键约束:保证了数据完整性
这是关系数据库中标准的一对多关系实现方式,通过在"多"的一方添加外键指向"一"的一方来实现。
我来为你画一个 Mermaid 图来理清物理关系和业务逻辑关系:
erDiagram
%% 业务逻辑关系(概念层)
DncMenu {
Guid Id "主键"
string Name "菜单名称"
string Icon "图标"
int Sort "排序"
}
DncPermission {
Guid Id "主键"
string Code "权限代码"
string Name "权限名称"
Guid MenuGuid "外键"
}
%% 物理表关系(数据层)
%% 一对多关系通过外键实现
DncMenu ||--o{ DncPermission : "1对多(业务层)"
%% 每个权限记录只能指向一个菜单记录
DncPermission }o--|| DncMenu : "1对1(物理层)"
classDiagram
%% 业务对象模型
class DncMenu {
+Guid Id
+string Name
+string Icon
+int Sort
+ICollection~DncPermission~ Permissions
+GetPermissions() List~DncPermission~
}
class DncPermission {
+Guid Id
+string Code
+string Name
+Guid MenuGuid
+DncMenu Menu
+GetMenu() DncMenu
}
%% 业务关系:一对多
DncMenu "1" *-- "*" DncPermission : 业务逻辑关系
note for DncMenu "一个菜单可以有多个权限"
note for DncPermission "一个权限只能属于一个菜单"
graph LR
subgraph "物理数据库层面"
M1[菜单表记录1<br/>用户管理]
M2[菜单表记录2<br/>系统设置]
P1[权限记录1<br/>user.view]
P2[权限记录2<br/>user.edit]
P3[权限记录3<br/>system.config]
M1 --> P1
M1 --> P2
M2 --> P3
end
subgraph "业务逻辑层面"
UserMenu[用户管理菜单]
SystemMenu[系统设置菜单]
UserMenu --> |包含| ViewPerm[查看权限]
UserMenu --> |包含| EditPerm[编辑权限]
SystemMenu --> |包含| ConfigPerm[配置权限]
end
style M1 fill:#e1f5fe
style M2 fill:#e1f5fe
style P1 fill:#f3e5f5
style P2 fill:#f3e5f5
style P3 fill:#f3e5f5
3. 关系总结
- 数据库设计:在权限表中添加外键指向菜单表(标准的一对多实现)
- 查询方式:从菜单找权限是"一对多",从权限找菜单是"一对一"
- 本质:这是关系数据库中标准的一对多关系模式,没有混乱
DncMenu 实体结构图,如下
classDiagram
class DncMenu {
+Guid Guid
+string Name
+string Url
+string Alias
+string Icon
+Guid? ParentGuid
+string ParentName
+int Level
+string Description
+int Sort
+Status Status
+IsDeleted IsDeleted
+YesOrNo IsDefaultRouter
+DateTime CreatedOn
+Guid CreatedByUserGuid
+string CreatedByUserName
+DateTime? ModifiedOn
+Guid? ModifiedByUserGuid
+string ModifiedByUserName
+string Component
+YesOrNo? HideInMenu
+YesOrNo? NotCache
+string BeforeCloseFun
+ICollection~DncPermission~ Permissions
+YesOrNo IsSeed
}
class DncPermission {
+Guid Guid
+string Code
+string Name
}
DncMenu "1" --> "*" DncPermission : 拥有
erDiagram
DncMenu ||--o{ DncPermission : "拥有"
DncMenu {
guid Guid PK
string Name "菜单名称(50)"
string Url "链接地址(255)"
string Alias "页面别名(255)"
string Icon "菜单图标(128)"
guid ParentGuid "父级GUID"
string ParentName "上级菜单名称"
int Level "菜单层级"
string Description "描述(800)"
int Sort "排序"
int Status "是否可用"
int IsDeleted "是否已删"
int IsDefaultRouter "默认路由"
datetime CreatedOn "创建时间"
guid CreatedByUserGuid "创建者ID"
string CreatedByUserName "创建者姓名"
datetime ModifiedOn "修改时间"
guid ModifiedByUserGuid "修改者ID"
string ModifiedByUserName "修改者姓名"
string Component "前端组件(255)"
int HideInMenu "隐藏菜单"
int NotCache "不缓存"
string BeforeCloseFun "关闭回调(255)"
int IsSeed "种子数据"
}
graph LR
A[基本信息] --> A1[Guid]
A --> A2[Name]
A --> A3[Url]
A --> A4[Alias]
A --> A5[Icon]
B[层级结构] --> B1[ParentGuid]
B --> B2[ParentName]
B --> B3[Level]
C[基础属性] --> C1[Description]
C --> C2[Sort]
C --> C3[Status]
C --> C4[IsDeleted]
C --> C5[IsDefaultRouter]
D[审计字段] --> D1[CreatedOn]
D --> D2[CreatedByUserGuid]
D --> D3[CreatedByUserName]
D --> D4[ModifiedOn]
D --> D5[ModifiedByUserGuid]
D --> D6[ModifiedByUserName]
E[前端相关] --> E1[Component]
E --> E2[HideInMenu]
E --> E3[NotCache]
E --> E4[BeforeCloseFun]
F[关联字段] --> F1[Permissions]
F --> F2[IsSeed]
DncMenu --> A
DncMenu --> B
DncMenu --> C
DncMenu --> D
DncMenu --> E
DncMenu --> F

浙公网安备 33010602011771号