双库协同,各取所长:.NET Core 中 PostgreSQL 与 SQLite 的优雅融合实战

9 阅读3分钟

在现代应用开发中,单一数据库架构往往难以满足所有场景需求。例如,你可能希望:

  • 主业务数据存入功能强大、支持高并发的 PostgreSQL
  • 本地缓存、边缘计算或嵌入式日志使用轻量、零配置的 SQLite

这种“混合持久化”(Polyglot Persistence)策略正变得越来越普遍。而 .NET Core 凭借其强大的依赖注入(DI)体系和 Entity Framework Core(EF Core)的多上下文支持,为双数据库架构提供了优雅的实现路径。

本文将手把手带你构建一个同时使用 PostgreSQL(主库)SQLite(辅助库) 的 .NET Core 应用,并分享生产级的最佳实践。


一、为什么选择 PostgreSQL + SQLite?

场景PostgreSQLSQLite
数据规模TB 级,多用户并发GB 级,单机/边缘
部署复杂度需独立服务零配置,文件即数据库
事务与一致性完整 ACID,支持分布式事务单文件 ACID,不支持并发写
典型用途用户账户、订单、核心业务本地缓存、设备日志、临时任务队列

✅ 组合优势:
主业务强一致 + 边缘数据轻量化 = 架构弹性与性能兼顾


二、项目结构设计

我们将创建两个独立的 DbContext,分别对应不同数据库:

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 包

dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore.Sqlite

注意:不要混用同一个 DbContext 注册多个提供程序!

2. 定义模型(Models)

// 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

// 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 中注册双上下文

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 配置:

{
  "ConnectionStrings": {
    "Postgres": "Host=localhost;Database=myapp;Username=postgres;Password=secret",
    "Sqlite": "Data Source=device_logs.db"
  }
}

5. 编写服务层,按需注入

// 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();
    }
}

控制器中使用:

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 单独启用迁移:

    dotnet 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 的极致轻量。

双库协同,不是妥协,而是智慧——让每一份数据,都落在最适合它的土壤上。