谈谈EF的两种模式
EF Core 支持两种主要开发模式:Code First 和 Database First。让我详细分析两者的使用场景和特点:
一、Code First 模式
工作流程
实体类 → 配置 → 迁移 → 数据库
代码示例
// 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. 新项目开发
// 团队从零开始设计领域模型
// 数据库结构尚未存在
public class ECommerceSystem
{
// 优先考虑业务对象设计
public class ShoppingCart { /* 购物车逻辑优先 */ }
public class Payment { /* 支付领域模型 */ }
public class Inventory { /* 库存管理模型 */ }
// 数据库是实现的细节
}
2. 敏捷开发/快速迭代
// 需求频繁变更时
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)项目
// 以领域模型为核心
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. 团队拥有数据库完全控制权
# 开发团队可以自由:
# 1. 创建新表
# 2. 修改列类型
# 3. 添加索引
# 4. 删除不再使用的列
# 不需要与DBA协调
优点
-
完全控制:数据库结构完全由代码控制
-
版本控制友好:迁移文件可纳入 Git
-
快速迭代:修改实体 → 生成迁移 → 更新数据库
-
领域驱动:优先关注业务模型而非数据库结构
二、Database First 模式
工作流程
现有数据库 → 反向工程 → 实体类 → 使用
使用方式
# 1. 从现有数据库生成实体
dotnet ef dbcontext scaffold "Server=.;Database=ExistingDB;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models
# 2. 或使用Visual Studio
# 右键项目 → 添加 → 新建项 → ADO.NET实体数据模型
适用场景
1. 遗留系统集成
-- 已有复杂的数据库结构
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设计完成
// 场景:DBA已优化数据库设计
// 索引策略已实施
// 分区方案已制定
// 团队需要基于现有设计开发
// 生成的实体可能不完全符合OO原则,但反映真实表结构
public partial class LegacyCustomer
{
public string CustCode { get; set; } // 数据库字段名
public string CustName { get; set; }
public DateTime? RegDate { get; set; } // 可空类型
}
3. 与其他系统共享数据库
-- 多个应用使用同一个数据库
-- 修改需要协调
-- App1(Java应用) <---> 共享数据库 <---> App2(.NET应用)
-- App3(报表系统) <---> <---> App4(数据仓库)
4. 性能优化优先的场景
-- 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);
优点
-
利用现有投资:重用已有的数据库设计和优化
-
减少学习成本:数据库结构已知
-
与其他系统兼容:保持数据库结构一致性
-
专业设计:DBA设计的数据库通常更优化
三、详细对比
| 方面 | Code First | Database First |
|---|---|---|
| 起点 | 领域模型/实体类 | 现有数据库 |
| 控制权 | 开发团队 | DBA/数据库团队 |
| 适合团队 | 全栈团队 | 分工明确的大团队 |
| 变更流程 | 代码 → 迁移 → 数据库 | 数据库 → 重新生成实体 |
| 版本控制 | 迁移文件可版本化 | 只有实体类可版本化 |
| 数据库优化 | 需要团队自己优化 | 通常已由DBA优化 |
| 重构灵活性 | 高(可重命名、重构) | 低(依赖现有结构) |
四、混合模式与实际案例
案例1:微服务架构
// 每个微服务使用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:大型企业系统改造
// 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:多数据库支持
// 产品需要支持多种数据库
// 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 当:
-
🚀 启动新项目,数据库设计可完全控制
-
🔄 频繁变更需求,需要快速迭代
-
🏗️ 实施DDD,领域模型是核心
-
👨💻 小团队或全栈团队,没有专门的DBA
-
📱 开发移动或桌面应用,使用SQLite等嵌入式数据库
选择 Database First 当:
-
🏛️ 集成遗留系统,已有复杂的数据库
-
👥 大型组织,有专门的DBA团队
-
🔗 多个系统共享数据库,需要保持结构稳定
-
⚡ 性能关键系统,数据库已专门优化
-
📊 报表和分析系统,基于现有数据仓库
实际建议:
// 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; }
// 通过迁移逐步重构
}
迁移策略
// 从Database First过渡到Code First的步骤:
// 1. 使用反向工程生成初始实体
// 2. 创建初始迁移作为基准
// 3. 逐步重构实体类
// 4. 添加新的迁移
// 5. 最终获得对数据库的代码控制
关键点:选择哪种模式主要取决于项目起点、团队结构和现有投资。现代开发中,Code First 更受欢迎,因为它提供了更好的开发体验和更灵活的重构能力。但对于必须与现有系统集成的场景,Database First 是必要的起点。

浙公网安备 33010602011771号