ORM会不会刷关联属性

📚 EF Core导航属性:为什么它不会成为数据库字段

🔍 问题起源

作为Java开发者转.NET,我们习惯了JPA/Hibernate中的关联映射。在Java中,我们经常这样写:

java 复制代码
@Entity
public class Order {
    @Id
    private Long id;
    
    @OneToMany(mappedBy = "order")
    private List<OrderItem> orderItems;  // 这个不会成为数据库字段吗?
}

在.NET ABP项目中,我们也看到了类似的写法,但是我在新微服务项目中用的是DDD开发思想,但是用ORM刷数据库表的时候会不会把关联熟悉刷进去:

csharp 复制代码
public class Commission : FullAuditedAggregateRoot<long>
{
    // 这个会变成数据库字段吗?
    public virtual ICollection<CommissionAttachment> Attachments { get; set; }
}

🎯 核心结论

导航属性(如 ICollection<T>)不会直接映射为数据库表的字段!

🏗️ 数据库 vs 代码:两种不同的存在形式

1. 数据库层面:表和关系

sql 复制代码
-- Commissions表(实际存储)
CREATE TABLE Commissions (
    Id BIGINT PRIMARY KEY,
    ContractNumber NVARCHAR(100),  -- 实际字段
    ContractAmt DECIMAL(18,2),     -- 实际字段
    ... -- 其他实际字段
    -- 没有 Attachments 字段!
);

-- CommissionAttachments表(实际存储)
CREATE TABLE CommissionAttachments (
    Id BIGINT PRIMARY KEY,
    CommissionId BIGINT,           -- 外键(实际字段)
    FileName NVARCHAR(255),        -- 实际字段
    -- 外键约束建立关联
    FOREIGN KEY (CommissionId) REFERENCES Commissions(Id)
);

2. 代码层面:对象和导航

csharp 复制代码
// 实体类 - 业务对象表示
public class Commission
{
    // 这些会映射到数据库字段
    public long Id { get; set; }
    public string ContractNumber { get; set; }
    public decimal? ContractAmt { get; set; }
    
    // 这个不会映射到数据库字段!
    // 它只是代码层面的对象引用
    public virtual ICollection<CommissionAttachment> Attachments { get; set; }
}

🔄 EF Core的"魔法":如何建立关联

方式1:通过外键属性(最清晰)

csharp 复制代码
public class CommissionAttachment
{
    public long Id { get; set; }
    public string FileName { get; set; }
    
    // 这个才是数据库字段!
    public long CommissionId { get; set; }  // 外键列
    
    // 导航属性(用于代码中访问关联对象)
    public virtual Commission Commission { get; set; }
}

方式2:通过配置(在DbContext中)

csharp 复制代码
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Commission>(entity =>
    {
        // 告诉EF Core:Commission有多个Attachment
        entity.HasMany(c => c.Attachments)
              // 每个Attachment属于一个Commission
              .WithOne(a => a.Commission)
              // 外键是CommissionAttachment表的CommissionId字段
              .HasForeignKey(a => a.CommissionId);
    });
}

📊 数据库迁移的实际生成

当你运行 dotnet ef migrations add 时,生成的是:

sql 复制代码
-- 创建主表(没有导航属性)
CREATE TABLE [Commissions] (
    [Id] bigint NOT NULL IDENTITY,
    [ContractNumber] nvarchar(100) NULL,
    [ContractAmt] decimal(18,2) NULL,
    ...其他实际字段,
    CONSTRAINT [PK_Commissions] PRIMARY KEY ([Id])
);

-- 创建关联表(包含外键)
CREATE TABLE [CommissionAttachments] (
    [Id] bigint NOT NULL IDENTITY,
    [FileName] nvarchar(255) NULL,
    [CommissionId] bigint NOT NULL,  -- ← 这才是关键!
    CONSTRAINT [PK_CommissionAttachments] PRIMARY KEY ([Id]),
    -- 外键约束建立关系
    CONSTRAINT [FK_CommissionAttachments_Commissions_CommissionId] 
        FOREIGN KEY ([CommissionId]) REFERENCES [Commissions] ([Id])
        ON DELETE CASCADE
);

-- 创建索引提高查询性能
CREATE INDEX [IX_CommissionAttachments_CommissionId] 
    ON [CommissionAttachments] ([CommissionId]);

💡 理解这个设计的好处

1. 数据库规范化

  • 避免数据冗余
  • 保持表结构简洁
  • 符合关系型数据库设计原则

2. 对象关系映射的真正意义

csharp 复制代码
// 代码中:直观的对象访问
var commission = GetCommission(123);
foreach (var attachment in commission.Attachments)
{
    Console.WriteLine(attachment.FileName);
}

// 底层实际执行(大致相当于):
// 1. SELECT * FROM Commissions WHERE Id = 123
// 2. SELECT * FROM CommissionAttachments WHERE CommissionId = 123

3. 灵活性

  • 可以延迟加载(Lazy Loading)
  • 可以预先加载(Eager Loading)
  • 可以显式加载(Explicit Loading)

🧪 验证实验:创建简单的测试

步骤1:创建两个相关实体

csharp 复制代码
public class Parent
{
    public int Id { get; set; }
    public string Name { get; set; }
    
    // 导航属性
    public virtual ICollection<Child> Children { get; set; }
}

public class Child
{
    public int Id { get; set; }
    public string Name { get; set; }
    
    // 外键
    public int ParentId { get; set; }
    
    // 导航属性
    public virtual Parent Parent { get; set; }
}

步骤2:运行迁移

bash 复制代码
dotnet ef migrations add CreateParentChild
dotnet ef database update

步骤3:查看生成的SQL

你会看到:

  • Parents 表没有 Children 字段
  • Children 表有 ParentId 字段作为外键
  • 外键约束建立了表间关系

🎯 实际开发中的正确理解

作为开发者,你应该这样想:

  1. 数据库表存储数据

    • 存储实际的数据值
    • 通过外键建立表间关系
    • 符合SQL和关系代数
  2. 代码实体表示业务对象

    • 包含数据字段(映射到表列)
    • 包含导航属性(表示对象关系)
    • 提供业务逻辑和方法
  3. EF Core负责桥梁作用

    • 将对象操作转换为SQL
    • 将查询结果组装为对象图
    • 管理关联数据的加载策略

📝 总结要点

  1. 导航属性是纯代码概念,用于在内存中建立对象间关联
  2. 数据库通过外键字段和约束建立表间关系
  3. EF Core自动管理代码对象和数据库表之间的映射
  4. 迁移只生成实际的表和列,不会生成"虚拟"的导航属性字段

💭 思考题

如果你有一个 Order 实体,需要关联100个 OrderItem,数据库表中:

  • Order 表不会有一个包含100个item的字段
  • ✅ 会有100行记录在 OrderItems 表中
  • ✅ 每行 OrderItems 都有一个 OrderId 指向父订单
  • ✅ 代码中通过 order.Items 可以方便地访问这100个item

这就是ORM的威力:让代码更符合面向对象思维,让数据库保持关系型结构的优势


记住:导航属性是你的代码助手,外键是数据库的关联机制。两者配合,各司其职。

相关推荐
光影34151 小时前
购买 orangepiB 主板 ubuntu 系统 联网
数据库·ubuntu·postgresql
VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue毕业设计选题管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
袁煦丞 cpolar内网穿透实验室1 小时前
12.1周一 Postgresql无需公网 IP 也能远程连数据库?cpolar 内网穿透实验室第 402 个成功挑战
数据库·tcp/ip·postgresql·远程工作·内网穿透·cpolar
葡萄城技术团队1 小时前
不知道怎么选型文件型数据库?快来看看吧
数据库
生产队队长1 小时前
Database:PLSQL连接Oracle数据库(两种方式)
数据库·oracle
qq_433192181 小时前
Linux ISCSI服务器配置
linux·服务器·数据库
y***13641 小时前
【MySQL】MVCC详解, 图文并茂简单易懂
android·数据库·mysql
u***45161 小时前
数据库Redis数据库
数据库·redis·缓存
专注API从业者1 小时前
Node.js/Python 调用淘宝关键词搜索 API:从接入到数据解析完整指南
开发语言·数据结构·数据库·node.js