在现代应用开发中,单一数据库架构往往难以满足所有场景需求。例如,你可能希望:
- 主业务数据 存入功能强大、支持高并发的 PostgreSQL;
- 本地缓存、边缘计算或嵌入式日志 使用轻量、零配置的 SQLite。
这种"混合持久化"(Polyglot Persistence)策略正变得越来越普遍。而 .NET Core 凭借其强大的依赖注入(DI)体系和 Entity Framework Core(EF Core)的多上下文支持,为双数据库架构提供了优雅的实现路径。
本文将手把手带你构建一个同时使用 PostgreSQL(主库) 和 SQLite(辅助库) 的 .NET Core 应用,并分享生产级的最佳实践。
一、为什么选择 PostgreSQL + SQLite?
| 场景 | PostgreSQL | SQLite |
|---|---|---|
| 数据规模 | TB 级,多用户并发 | GB 级,单机/边缘 |
| 部署复杂度 | 需独立服务 | 零配置,文件即数据库 |
| 事务与一致性 | 完整 ACID,支持分布式事务 | 单文件 ACID,不支持并发写 |
| 典型用途 | 用户账户、订单、核心业务 | 本地缓存、设备日志、临时任务队列 |
✅ 组合优势:
主业务强一致 + 边缘数据轻量化 = 架构弹性与性能兼顾
二、项目结构设计
我们将创建两个独立的 DbContext,分别对应不同数据库:
arduino
MyApp/
├── Data/
│ ├── MainDbContext.cs // PostgreSQL
│ └── LocalDbContext.cs // SQLite
├── Models/
│ ├── User.cs // 存于 PostgreSQL
│ └── DeviceLog.cs // 存于 SQLite
├── Services/
│ ├── UserService.cs
│ └── LogService.cs
└── Program.cs
三、Step-by-Step 实现
1. 安装必要 NuGet 包
csharp
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
注意:不要混用同一个 DbContext 注册多个提供程序!
2. 定义模型(Models)
csharp
// Models/User.cs
public class User
{
public int Id { get; set; }
public string Name { get; set; } = default!;
public string Email { get; set; } = default!;
}
// Models/DeviceLog.cs
public class DeviceLog
{
public long Id { get; set; }
public DateTime Timestamp { get; set; }
public string Message { get; set; } = default!;
}
3. 创建两个 DbContext
arduino
// Data/MainDbContext.cs
public class MainDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public MainDbContext(DbContextOptions<MainDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable("users");
}
}
// Data/LocalDbContext.cs
public class LocalDbContext : DbContext
{
public DbSet<DeviceLog> DeviceLogs { get; set; }
public LocalDbContext(DbContextOptions<LocalDbContext> options) : base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// 可选:若未通过 DI 注入连接字符串
// optionsBuilder.UseSqlite("Data Source=device_logs.db");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DeviceLog>().ToTable("device_logs");
}
}
4. 在 Program.cs 中注册双上下文
ini
var builder = WebApplication.CreateBuilder(args);
// 注册 PostgreSQL 上下文
builder.Services.AddDbContext<MainDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("Postgres")));
// 注册 SQLite 上下文
builder.Services.AddDbContext<LocalDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("Sqlite")));
// 自动创建数据库(仅开发环境建议)
using var scope = builder.Services.BuildServiceProvider().CreateScope();
scope.ServiceProvider.GetRequiredService<MainDbContext>().Database.EnsureCreated();
scope.ServiceProvider.GetRequiredService<LocalDbContext>().Database.EnsureCreated();
appsettings.json 配置:
json
{
"ConnectionStrings": {
"Postgres": "Host=localhost;Database=myapp;Username=postgres;Password=secret",
"Sqlite": "Data Source=device_logs.db"
}
}
5. 编写服务层,按需注入
csharp
// Services/UserService.cs
public class UserService
{
private readonly MainDbContext _db;
public UserService(MainDbContext db) => _db = db;
public async Task<User> CreateUser(string name, string email)
{
var user = new User { Name = name, Email = email };
_db.Users.Add(user);
await _db.SaveChangesAsync();
return user;
}
}
// Services/LogService.cs
public class LogService
{
private readonly LocalDbContext _localDb;
public LogService(LocalDbContext localDb) => _localDb = localDb;
public async Task LogMessage(string msg)
{
_localDb.DeviceLogs.Add(new DeviceLog
{
Timestamp = DateTime.UtcNow,
Message = msg
});
await _localDb.SaveChangesAsync();
}
}
控制器中使用:
dart
app.MapPost("/user", async (UserService userService, HttpRequest req) =>
{
var user = await userService.CreateUser("Alice", "alice@example.com");
return Results.Ok(user);
});
app.MapPost("/log", async (LogService logService, [FromBody] string msg) =>
{
await logService.LogMessage(msg);
return Results.Ok();
});
四、最佳实践与避坑指南
✅ 1. 严格分离关注点
- 不要在同一个业务方法中同时操作两个 DbContext(除非必要);
- 若需跨库事务,考虑最终一致性(如通过消息队列补偿)。
✅ 2. 连接字符串安全
- PostgreSQL 密码勿硬编码,使用 Secret Manager 或 Azure Key Vault;
- SQLite 路径建议使用
Path.Combine(Directory.GetCurrentDirectory(), "data", "logs.db")确保可移植性。
✅ 3. 迁移(Migrations)管理
-
为每个 DbContext 单独启用迁移:
csharpdotnet ef migrations add InitialMain -c MainDbContext -o Migrations/Postgres dotnet ef migrations add InitialLocal -c LocalDbContext -o Migrations/Sqlite -
生产环境建议手动审核 SQL 脚本。
✅ 4. 性能与资源
- SQLite 默认不支持高并发写入,避免在 Web API 热点路径频繁写日志;
- 可搭配
Microsoft.Data.Sqlite的 WAL 模式提升并发读性能。
五、适用场景推荐
- 📱 IoT 边缘设备 + 云中心:设备用 SQLite 记录本地状态,定期同步到云端 PostgreSQL;
- 🖥️ 桌面应用:配置、缓存用 SQLite,用户数据同步到 PostgreSQL;
- 🧪 测试隔离:集成测试时用 SQLite 替代 PostgreSQL,加速执行。
结语
在 .NET Core 中融合 PostgreSQL 与 SQLite,并非"炫技",而是对场景适配性的理性选择。通过清晰的架构分层、独立的 DbContext 设计和合理的服务注入,我们既能享受 PostgreSQL 的企业级能力,又能利用 SQLite 的极致轻量。
双库协同,不是妥协,而是智慧------让每一份数据,都落在最适合它的土壤上。