你的想法很重要-大家都来谈 (C)Copyright 2012 by WeiPt

谈谈EF的两种模式

EF Core 支持两种主要开发模式:Code First 和 Database First。让我详细分析两者的使用场景和特点:

一、Code First 模式

工作流程

text
实体类 → 配置 → 迁移 → 数据库

代码示例

csharp
// 1. 定义实体
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public DateTime CreatedDate { get; set; }
}

// 2. DbContext
public class AppDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>()
            .Property(p => p.Name)
            .IsRequired()
            .HasMaxLength(200);
    }
}

// 3. 生成迁移
// dotnet ef migrations add InitialCreate
// dotnet ef database update

适用场景

1. 新项目开发

csharp
// 团队从零开始设计领域模型
// 数据库结构尚未存在
public class ECommerceSystem
{
    // 优先考虑业务对象设计
    public class ShoppingCart { /* 购物车逻辑优先 */ }
    public class Payment { /* 支付领域模型 */ }
    public class Inventory { /* 库存管理模型 */ }
    
    // 数据库是实现的细节
}

2. 敏捷开发/快速迭代

csharp
// 需求频繁变更时
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    // 新增需求:添加社交媒体账号
    public string? TwitterHandle { get; set; }
    public string? LinkedInUrl { get; set; }
    
    // 只需修改实体,然后:
    // dotnet ef migrations add AddSocialMediaColumns
    // dotnet ef database update
}

3. 领域驱动设计(DDD)项目

csharp
// 以领域模型为核心
public class Order : Entity
{
    private Order() {} // 私有构造函数保护不变性
    
    public static Order Create(Customer customer)
    {
        // 业务规则验证
        var order = new Order
        {
            Customer = customer,
            Status = OrderStatus.Draft,
            OrderNumber = GenerateOrderNumber()
        };
        return order;
    }
    
    // 值对象和聚合根
    public Address ShippingAddress { get; private set; }
    public Money TotalAmount { get; private set; }
    
    // 行为方法
    public void AddItem(Product product, int quantity)
    {
        // 业务逻辑
    }
}

4. 团队拥有数据库完全控制权

bash
# 开发团队可以自由:
# 1. 创建新表
# 2. 修改列类型
# 3. 添加索引
# 4. 删除不再使用的列
# 不需要与DBA协调

优点

  1. 完全控制:数据库结构完全由代码控制

  2. 版本控制友好:迁移文件可纳入 Git

  3. 快速迭代:修改实体 → 生成迁移 → 更新数据库

  4. 领域驱动:优先关注业务模型而非数据库结构

二、Database First 模式

工作流程

text
现有数据库 → 反向工程 → 实体类 → 使用

使用方式

bash
# 1. 从现有数据库生成实体
dotnet ef dbcontext scaffold "Server=.;Database=ExistingDB;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models

# 2. 或使用Visual Studio
# 右键项目 → 添加 → 新建项 → ADO.NET实体数据模型

适用场景

1. 遗留系统集成

sql
-- 已有复杂的数据库结构
CREATE TABLE legacy_orders (
    order_id INT PRIMARY KEY,
    cust_code VARCHAR(20),
    order_date DATETIME,
    -- 复杂的业务规则已存储在存储过程中
    total_amount DECIMAL(18,2)
);

-- 已有存储过程、视图、函数
CREATE PROCEDURE sp_GetMonthlyReport
    @year INT,
    @month INT
AS
BEGIN
    -- 复杂的业务逻辑
END

2. 数据库已由DBA设计完成

csharp
// 场景:DBA已优化数据库设计
// 索引策略已实施
// 分区方案已制定
// 团队需要基于现有设计开发

// 生成的实体可能不完全符合OO原则,但反映真实表结构
public partial class LegacyCustomer
{
    public string CustCode { get; set; }  // 数据库字段名
    public string CustName { get; set; }
    public DateTime? RegDate { get; set; } // 可空类型
}

3. 与其他系统共享数据库

sql
-- 多个应用使用同一个数据库
-- 修改需要协调
-- App1(Java应用)  <--->  共享数据库  <--->  App2(.NET应用)
-- App3(报表系统)  <--->                    <--->  App4(数据仓库)

4. 性能优化优先的场景

sql
-- DBA已对查询性能进行专门优化
CREATE TABLE optimized_table (
    id INT IDENTITY(1,1),
    data NVARCHAR(MAX),
    created DATETIME DEFAULT GETDATE(),
    -- 精心设计的索引
    INDEX ix_created (created),
    -- 计算列
    data_length AS LEN(data),
    -- 分区方案
    CONSTRAINT pk_optimized PRIMARY KEY (id)
) ON partition_scheme(created);

优点

  1. 利用现有投资:重用已有的数据库设计和优化

  2. 减少学习成本:数据库结构已知

  3. 与其他系统兼容:保持数据库结构一致性

  4. 专业设计:DBA设计的数据库通常更优化

三、详细对比

 
方面Code FirstDatabase First
起点 领域模型/实体类 现有数据库
控制权 开发团队 DBA/数据库团队
适合团队 全栈团队 分工明确的大团队
变更流程 代码 → 迁移 → 数据库 数据库 → 重新生成实体
版本控制 迁移文件可版本化 只有实体类可版本化
数据库优化 需要团队自己优化 通常已由DBA优化
重构灵活性 高(可重命名、重构) 低(依赖现有结构)

四、混合模式与实际案例

案例1:微服务架构

csharp
// 每个微服务使用Code First
// 服务间通过API通信,不直接访问彼此的数据库

// 订单服务(独立数据库)
public class OrderDbContext : DbContext
{
    // Code First设计订单领域
    public DbSet<Order> Orders { get; set; }
}

// 库存服务(独立数据库)
public class InventoryDbContext : DbContext
{
    // Code First设计库存领域
    public DbSet<InventoryItem> Items { get; set; }
}

案例2:大型企业系统改造

csharp
// 1. 现有核心业务表(Database First)
public partial class LegacyCustomer
{
    // 从现有数据库生成
}

// 2. 新功能模块(Code First)
public class NewFeatureEntity
{
    // 使用Code First开发新功能
    // 可以慢慢迁移旧数据到新结构
}

// 3. 使用DbContext组合
public class HybridDbContext : DbContext
{
    public DbSet<LegacyCustomer> LegacyCustomers { get; set; }
    public DbSet<NewFeatureEntity> NewFeatures { get; set; }
}

案例3:多数据库支持

csharp
// 产品需要支持多种数据库
// Code First更灵活
public class MultiDbDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        if (DatabaseType == "SQLServer")
            options.UseSqlServer(connectionString);
        else if (DatabaseType == "PostgreSQL")
            options.UseNpgsql(connectionString);
        // 同一套实体,不同数据库
    }
}

五、选择建议

选择 Code First 当:

  1. 🚀 启动新项目,数据库设计可完全控制

  2. 🔄 频繁变更需求,需要快速迭代

  3. 🏗️ 实施DDD,领域模型是核心

  4. 👨‍💻 小团队或全栈团队,没有专门的DBA

  5. 📱 开发移动或桌面应用,使用SQLite等嵌入式数据库

选择 Database First 当:

  1. 🏛️ 集成遗留系统,已有复杂的数据库

  2. 👥 大型组织,有专门的DBA团队

  3. 🔗 多个系统共享数据库,需要保持结构稳定

  4. ⚡ 性能关键系统,数据库已专门优化

  5. 📊 报表和分析系统,基于现有数据仓库

实际建议:

csharp
// 1. 新项目建议从Code First开始
//    更容易适应变化

// 2. 对于复杂查询,可以使用Database First生成视图对应的实体
public class ReportViewModel
{
    // 基于数据库视图生成的实体
    // 用于复杂报表,不用于更新
}

// 3. 混合使用:核心业务用Database First,新功能用Code First
public class HybridApproach
{
    // 旧系统集成
    public DbSet<LegacyOrder> LegacyOrders { get; set; }
    
    // 新功能开发
    public DbSet<NewFeature> NewFeatures { get; set; }
    
    // 通过迁移逐步重构
}

迁移策略

csharp
// 从Database First过渡到Code First的步骤:
// 1. 使用反向工程生成初始实体
// 2. 创建初始迁移作为基准
// 3. 逐步重构实体类
// 4. 添加新的迁移
// 5. 最终获得对数据库的代码控制

关键点:选择哪种模式主要取决于项目起点、团队结构和现有投资。现代开发中,Code First 更受欢迎,因为它提供了更好的开发体验和更灵活的重构能力。但对于必须与现有系统集成的场景,Database First 是必要的起点。

posted @ 2026-01-15 09:39  weipt  阅读(1)  评论(0)    收藏  举报

你的想法很重要-大家都来谈 (C)Copyright 2012 by WeiPt