Appearance
多 DbContext 场景
概述
在复杂应用中,单个 DbContext 往往无法满足所有需求。多 DbContext 架构允许将数据访问层按业务边界、性能需求或安全隔离进行拆分。
典型应用场景
| 场景 | 说明 | 优势 |
|---|---|---|
| 读写分离 | 主库写、从库读 | 提升读取性能,分散负载 |
| CQRS 架构 | 命令端和查询端分离 | 优化各自的模型和性能 |
| 微服务拆分 | 不同服务使用独立数据库 | 解耦、独立扩展 |
| 多租户隔离 | 每个租户独立数据库 | 数据安全、灵活配置 |
| 遗留系统集成 | 新旧系统数据库并存 | 渐进式迁移 |
| 审计日志分离 | 业务数据和审计数据分开 | 性能优化、合规要求 |
基础配置
注册多个 DbContext
csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 订单数据库(主库)
builder.Services.AddDbContext<OrderDbContext>(options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString("OrderDb"),
sqlOptions => sqlOptions.EnableRetryOnFailure()));
// 产品数据库(从库)
builder.Services.AddDbContext<ProductDbContext>(options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString("ProductDb"),
sqlOptions => sqlOptions.EnableRetryOnFailure()));
// 用户数据库
builder.Services.AddDbContext<UserDbContext>(options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString("UserDb")));
var app = builder.Build();DbContext 定义
csharp
// OrderDbContext.cs
public class OrderDbContext : DbContext
{
public OrderDbContext(DbContextOptions<OrderDbContext> options)
: base(options)
{
}
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
public DbSet<Payment> Payments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(o => o.Id);
entity.Property(o => o.OrderNumber).IsRequired().HasMaxLength(50);
entity.HasIndex(o => o.OrderNumber).IsUnique();
});
// 其他配置...
}
}
// ProductDbContext.cs
public class ProductDbContext : DbContext
{
public ProductDbContext(DbContextOptions<ProductDbContext> options)
: base(options)
{
}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Inventory> Inventories { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>(entity =>
{
entity.HasKey(p => p.Id);
entity.Property(p => p.Name).IsRequired().HasMaxLength(200);
entity.HasIndex(p => p.Name);
});
// 其他配置...
}
}场景一: 读写分离
架构设计
┌─────────────┐
│ 应用层 │
└──────┬──────┘
│
├──────────────┐
│ │
┌──────▼──────┐ ┌────▼──────────┐
│ WriteDbContext│ │ ReadDbContext │
│ (主库-写) │ │ (从库-读) │
└──────┬──────┘ └────┬──────────┘
│ │
┌──────▼──────┐ ┌────▼──────────┐
│ Primary DB │ │ Replica DB(s) │
│ (SQL Server)│ │ (Read Only) │
└─────────────┘ └───────────────┘实现
csharp
// WriteDbContext.cs - 用于写操作
public class WriteDbContext : DbContext
{
public WriteDbContext(DbContextOptions<WriteDbContext> options)
: base(options)
{
}
public DbSet<Order> Orders { get; set; }
public DbSet<Product> Products { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// 确保不使用查询跟踪(写操作不需要)
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
}
// ReadDbContext.cs - 用于读操作
public class ReadDbContext : DbContext
{
public ReadDbContext(DbContextOptions<ReadDbContext> options)
: base(options)
{
}
public DbSet<Order> Orders { get; set; }
public DbSet<Product> Products { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// 默认无跟踪查询(提升读取性能)
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
}
// 注册
builder.Services.AddDbContext<WriteDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("WriteDb")));
builder.Services.AddDbContext<ReadDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("ReadDb"))
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));使用示例
csharp
// Minimal API
app.MapGet("/api/products", async (ReadDbContext readDb) =>
{
// 从从库读取
var products = await readDb.Products
.AsNoTracking()
.ToListAsync();
return Results.Ok(products);
});
app.MapPost("/api/products", async (WriteDbContext writeDb, Product product) =>
{
// 写入主库
writeDb.Products.Add(product);
await writeDb.SaveChangesAsync();
return Results.Created($"/api/products/{product.Id}", product);
});
// Service 层
public class ProductService
{
private readonly ReadDbContext _readDb;
private readonly WriteDbContext _writeDb;
public ProductService(ReadDbContext readDb, WriteDbContext writeDb)
{
_readDb = readDb;
_writeDb = writeDb;
}
// 查询 - 使用从库
public async Task<List<Product>> GetProductsAsync()
{
return await _readDb.Products.ToListAsync();
}
// 写入 - 使用主库
public async Task<int> CreateProductAsync(Product product)
{
_writeDb.Products.Add(product);
await _writeDb.SaveChangesAsync();
return product.Id;
}
}自动路由中间件
csharp
// CqrsMiddleware.cs
public class CqrsMiddleware
{
private readonly RequestDelegate _next;
public CqrsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// 根据 HTTP 方法选择数据库
if (context.Request.Method == HttpMethods.Get ||
context.Request.Method == HttpMethods.Head)
{
// GET 请求使用读库
context.Items["UseReadDb"] = true;
}
else
{
// POST/PUT/DELETE 使用写库
context.Items["UseReadDb"] = false;
}
await _next(context);
}
}
// Program.cs
app.UseMiddleware<CqrsMiddleware>();场景二: CQRS 架构
命令端与查询端分离
csharp
// CommandDbContext.cs - 命令端(写)
public class CommandDbContext : DbContext
{
public CommandDbContext(DbContextOptions<CommandDbContext> options)
: base(options)
{
}
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 命令端模型: 关注完整领域逻辑
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(o => o.Id);
entity.HasMany(o => o.Items)
.WithOne(i => i.Order)
.HasForeignKey(i => i.OrderId);
// 复杂的业务规则约束
entity.Property(o => o.Status)
.HasConversion<string>()
.HasMaxLength(20);
});
}
}
// QueryDbContext.cs - 查询端(读)
public class QueryDbContext : DbContext
{
public QueryDbContext(DbContextOptions<QueryDbContext> options)
: base(options)
{
}
// 查询端使用扁平化的 DTO 表
public DbSet<OrderSummary> OrderSummaries { get; set; }
public DbSet<ProductView> ProductViews { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 查询端模型: 针对读取优化
modelBuilder.Entity<OrderSummary>(entity =>
{
entity.HasNoKey(); // 视图无主键
entity.ToView("v_OrderSummaries"); // 映射到视图
// 添加索引优化查询
entity.HasIndex(e => e.CustomerId);
entity.HasIndex(e => e.OrderDate);
});
}
}
// 注册
builder.Services.AddDbContext<CommandDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDbContext<QueryDbContext>(options =>
options.UseSqlServer(connectionString)
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));MediatR 集成
csharp
// Commands/CreateOrderCommand.cs
public record CreateOrderCommand : IRequest<int>
{
public string CustomerId { get; set; } = null!;
public List<OrderItemRequest> Items { get; set; } = new();
}
public record OrderItemRequest
{
public int ProductId { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
// Commands/CreateOrderHandler.cs
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, int>
{
private readonly CommandDbContext _commandDb;
public CreateOrderHandler(CommandDbContext commandDb)
{
_commandDb = commandDb;
}
public async Task<int> Handle(CreateOrderCommand request, CancellationToken ct)
{
var order = new Order
{
CustomerId = request.CustomerId,
OrderDate = DateTime.UtcNow,
Status = OrderStatus.Pending
};
foreach (var item in request.Items)
{
order.Items.Add(new OrderItem
{
ProductId = item.ProductId,
Quantity = item.Quantity,
UnitPrice = item.UnitPrice
});
}
_commandDb.Orders.Add(order);
await _commandDb.SaveChangesAsync(ct);
return order.Id;
}
}
// Queries/GetOrderQuery.cs
public record GetOrderQuery(int OrderId) : IRequest<OrderDetailDto>;
public record OrderDetailDto
{
public int Id { get; set; }
public string CustomerId { get; set; } = null!;
public DateTime OrderDate { get; set; }
public string Status { get; set; } = null!;
public decimal TotalAmount { get; set; }
public List<OrderItemDto> Items { get; set; } = new();
}
public record OrderItemDto
{
public int ProductId { get; set; }
public string ProductName { get; set; } = null!;
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
// Queries/GetOrderHandler.cs
public class GetOrderHandler : IRequestHandler<GetOrderQuery, OrderDetailDto?>
{
private readonly QueryDbContext _queryDb;
public GetOrderHandler(QueryDbContext queryDb)
{
_queryDb = queryDb;
}
public async Task<OrderDetailDto?> Handle(GetOrderQuery request, CancellationToken ct)
{
// 从优化的视图查询
return await _queryDb.OrderSummaries
.Where(o => o.Id == request.OrderId)
.Select(o => new OrderDetailDto
{
Id = o.Id,
CustomerId = o.CustomerId,
OrderDate = o.OrderDate,
Status = o.Status,
TotalAmount = o.TotalAmount,
Items = o.Items.Select(i => new OrderItemDto
{
ProductId = i.ProductId,
ProductName = i.ProductName,
Quantity = i.Quantity,
UnitPrice = i.UnitPrice
}).ToList()
})
.FirstOrDefaultAsync(ct);
}
}
// Minimal API 集成
app.MapPost("/api/orders", async (
CreateOrderCommand command,
IMediator mediator,
CancellationToken ct) =>
{
var orderId = await mediator.Send(command, ct);
return Results.Created($"/api/orders/{orderId}", new { OrderId = orderId });
});
app.MapGet("/api/orders/{id}", async (
int id,
IMediator mediator,
CancellationToken ct) =>
{
var order = await mediator.Send(new GetOrderQuery(id), ct);
return order is not null ? Results.Ok(order) : Results.NotFound();
});场景三: 微服务拆分
独立数据库架构
┌─────────────────┐
│ API Gateway │
└────┬────┬────┬──┘
│ │ │
┌────▼──┐ ┌─▼──────┐ ┌─▼────────┐
│Order │ │Product │ │User │
│Service│ │Service │ │Service │
└────┬──┘ └─┬──────┘ └─┬────────┘
│ │ │
┌────▼──┐ ┌─▼──────┐ ┌─▼────────┐
│OrderDB│ │ProdDB │ │UserDB │
└───────┘ └────────┘ └──────────┘实现
csharp
// OrderService - Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<OrderDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("OrderDb")));
builder.Services.AddHttpClient<ProductServiceClient>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["Services:ProductApi"]);
});
var app = builder.Build();
app.MapGet("/api/orders/{id}/details", async (
int id,
OrderDbContext orderDb,
ProductServiceClient productClient,
CancellationToken ct) =>
{
// 从订单数据库获取订单
var order = await orderDb.Orders
.Include(o => o.Items)
.FirstOrDefaultAsync(o => o.Id == id, ct);
if (order == null)
return Results.NotFound();
// 通过 HTTP 调用产品服务获取产品信息
var productIds = order.Items.Select(i => i.ProductId).Distinct();
var products = await productClient.GetProductsByIdsAsync(productIds, ct);
// 组合结果
var orderDetails = new OrderDetailsDto
{
Order = order,
Products = products
};
return Results.Ok(orderDetails);
});场景四: 多租户隔离
每租户独立数据库
csharp
// TenantDbContext.cs
public class TenantDbContext : DbContext
{
private readonly string _tenantId;
public TenantDbContext(
DbContextOptions<TenantDbContext> options,
string tenantId)
: base(options)
{
_tenantId = tenantId;
}
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
}
// TenantDbContextFactory.cs
public class TenantDbContextFactory
{
private readonly IConfiguration _configuration;
public TenantDbContextFactory(IConfiguration configuration)
{
_configuration = configuration;
}
public TenantDbContext Create(string tenantId)
{
// 每个租户使用独立的连接字符串
var connectionString = _configuration.GetConnectionString($"Tenant_{tenantId}");
var options = new DbContextOptionsBuilder<TenantDbContext>()
.UseSqlServer(connectionString)
.Options;
return new TenantDbContext(options, tenantId);
}
}
// 使用
public class OrderService
{
private readonly TenantDbContextFactory _factory;
public OrderService(TenantDbContextFactory factory)
{
_factory = factory;
}
public async Task<List<Order>> GetTenantOrdersAsync(string tenantId)
{
using var context = _factory.Create(tenantId);
return await context.Orders.ToListAsync();
}
}跨 DbContext 事务
问题: 分布式事务
csharp
// ❌ 错误: 两个 DbContext 无法参与同一个事务
public async Task TransferDataAsync()
{
using var transaction = await _orderDb.Database.BeginTransactionAsync();
try
{
// 操作订单数据库
var order = new Order { /* ... */ };
_orderDb.Orders.Add(order);
await _orderDb.SaveChangesAsync();
// ❌ 产品数据库不在同一个事务中!
var product = await _productDb.Products.FindAsync(order.ProductId);
product.StockQuantity -= order.Quantity;
await _productDb.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}解决方案 1: 分布式事务
csharp
// ✅ 使用 TransactionScope(需要 MSDTC)
public async Task TransferDataAsync()
{
using var scope = new TransactionScope(
TransactionScopeAsyncFlowOption.Enabled);
try
{
var order = new Order { /* ... */ };
_orderDb.Orders.Add(order);
await _orderDb.SaveChangesAsync();
var product = await _productDb.Products.FindAsync(order.ProductId);
product.StockQuantity -= order.Quantity;
await _productDb.SaveChangesAsync();
scope.Complete(); // 提交分布式事务
}
catch
{
throw; // 自动回滚
}
}解决方案 2: 最终一致性(推荐)
csharp
// ✅ 使用事件驱动的最终一致性
public async Task CreateOrderAsync(Order order)
{
// 1. 保存订单
_orderDb.Orders.Add(order);
await _orderDb.SaveChangesAsync();
// 2. 发布领域事件
await _eventBus.PublishAsync(new OrderCreatedEvent
{
OrderId = order.Id,
ProductId = order.ProductId,
Quantity = order.Quantity
});
}
// 事件处理器 - 异步扣减库存
public class OrderCreatedEventHandler : IEventHandler<OrderCreatedEvent>
{
private readonly ProductDbContext _productDb;
public async Task HandleAsync(OrderCreatedEvent @event)
{
var product = await _productDb.Products.FindAsync(@event.ProductId);
if (product != null)
{
product.StockQuantity -= @event.Quantity;
await _productDb.SaveChangesAsync();
}
}
}最佳实践
✅ 推荐做法
1. 明确职责边界
csharp
// ✅ 清晰命名
public class OrderWriteDbContext { } // 明确是写操作
public class OrderReadDbContext { } // 明确是读操作
// ❌ 模糊命名
public class OrderContext1 { }
public class OrderContext2 { }2. 使用工厂模式管理多租户
csharp
// ✅ 工厂模式
public interface ITenantDbContextFactory
{
TenantDbContext Create(string tenantId);
}
// ❌ 硬编码
var context = new TenantDbContext(options, tenantId);3. 避免跨 DbContext 的 JOIN
csharp
// ❌ 错误: 尝试跨库 JOIN
var query = from o in _orderDb.Orders
join p in _productDb.Products on o.ProductId equals p.Id
select new { o, p };
// ✅ 正确: 分别查询后在内存中组合
var orders = await _orderDb.Orders.ToListAsync();
var productIds = orders.Select(o => o.ProductId).Distinct();
var products = await _productDb.Products
.Where(p => productIds.Contains(p.Id))
.ToDictionaryAsync(p => p.Id);
var result = orders.Select(o => new
{
Order = o,
Product = products[o.ProductId]
});❌ 常见陷阱
1. 忘记配置不同的连接字符串
csharp
// ❌ 错误: 两个 DbContext 使用同一个数据库
builder.Services.AddDbContext<OrderDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDbContext<ProductDbContext>(options =>
options.UseSqlServer(connectionString)); // ← 相同!
// ✅ 正确: 使用不同的连接字符串
builder.Services.AddDbContext<OrderDbContext>(options =>
options.UseSqlServer(orderConnectionString));
builder.Services.AddDbContext<ProductDbContext>(options =>
options.UseSqlServer(productConnectionString));2. 迁移冲突
bash
# ❌ 错误: 所有迁移混在一起
dotnet ef migrations add InitialCreate
# ✅ 正确: 为每个 DbContext 指定输出目录
dotnet ef migrations add InitialCreate \
--context OrderDbContext \
--output-dir Migrations/OrderDb
dotnet ef migrations add InitialCreate \
--context ProductDbContext \
--output-dir Migrations/ProductDb性能监控
监控多个 DbContext
csharp
public class MultiDbContextMonitor
{
private readonly ILogger<MultiDbContextMonitor> _logger;
public MultiDbContextMonitor(ILogger<MultiDbContextMonitor> logger)
{
_logger = logger;
}
public void MonitorDbContextPerformance(
string dbContextName,
TimeSpan executionTime,
int queryCount)
{
_logger.LogInformation(
"DbContext: {Name}, Queries: {Count}, Total Time: {Time}ms",
dbContextName,
queryCount,
executionTime.TotalMilliseconds);
if (executionTime > TimeSpan.FromSeconds(1))
{
_logger.LogWarning(
"Slow performance detected for {Name}: {Time}ms",
dbContextName,
executionTime.TotalMilliseconds);
}
}
}总结
多 DbContext 决策树
需要多个 DbContext?
│
├─ 读写性能差异大?
│ └─ ✅ 读写分离(主从库)
│
├─ 查询模型复杂?
│ └─ ✅ CQRS 架构
│
├─ 业务边界清晰?
│ └─ ✅ 微服务拆分
│
├─ 数据隔离要求高?
│ └─ ✅ 多租户(每租户独立库)
│
└─ 遗留系统集成?
└─ ✅ 新旧数据库并存核心要点
- 明确职责: 每个 DbContext 有清晰的边界
- 避免跨库事务: 优先使用最终一致性
- 独立迁移: 每个 DbContext 管理自己的迁移
- 监控性能: 分别监控各 DbContext 的性能指标
- 合理选择: 根据实际需求选择合适的拆分策略