Skip to content

多 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 架构
│
├─ 业务边界清晰?
│  └─ ✅ 微服务拆分
│
├─ 数据隔离要求高?
│  └─ ✅ 多租户(每租户独立库)
│
└─ 遗留系统集成?
   └─ ✅ 新旧数据库并存

核心要点 ​

  1. 明确职责: 每个 DbContext 有清晰的边界
  2. 避免跨库事务: 优先使用最终一致性
  3. 独立迁移: 每个 DbContext 管理自己的迁移
  4. 监控性能: 分别监控各 DbContext 的性能指标
  5. 合理选择: 根据实际需求选择合适的拆分策略

基于 MIT 许可发布