☰
.NET本地数据库选型全解析:SQLite、LiteDB、VistaDB对比与实战
2026/10/2 9:18:08 网站建设 项目流程

1. 项目背景:为什么我盯上了本地Db数据库选型

这段时间在重构一个基于 .NET 8 的桌面客户端项目,技术栈是 WPF + MVVM,数据规模不算大,单机使用为主,偶尔会有少量并发写入。项目早期用的是 SQLite,跑了一段时间之后发现几个麻烦事:部署时要额外打包原生 dll,不同操作系统下的兼容性要单独测,EF Core 映射部分类型时还有不少限制。于是我开始认真考虑一个问题——.NET 本地数据库到底怎么选才最省心。

先说清楚我这里说的“本地Db数据库”指的是什么。它和 SQL Server、MySQL 这类服务型数据库完全不是一个路数,服务型数据库要装服务、要配连接、要管账号权限,而本地嵌入式数据库是把数据库引擎以库文件形式直接链接进你的程序进程里,数据落在一个本地文件上,程序启动就能用,不需要额外的服务进程。.NET 生态里这类方案其实不少,常见的有 SQLite(经由 EF Core 或 Microsoft.Data.Sqlite 接入)、LiteDB、VistaDB、VistaDB 的轻量版等,再加上 SQL Server Express LocalDB 这种半嵌入式方案。

适合来看这篇文章的人,我猜大概有这么几类:一是做 WinForms / WPF 桌面应用、正在纠结本地数据存储方案的开发者;二是做 .NET MAUI 跨平台移动应用,想在离线场景下用本地库的朋友;三是从 .NET Framework 老项目往 .NET 6/8 迁移,需要顺便重新评估存储层的同学。这篇文章我会把 SQLite、LiteDB、VistaDB、LocalDB 这几个方案放在同一个天平上称一称,给出一套可以直接照抄的选型思路。

2. 本地数据库方案全景与选型坐标

2.1 一个都不能少:主流候选方案速览

动手选型之前,我给自己定了个规矩:先摸清候选池,再谈对比。下面这几个方案,是我筛选后的最终名单,每一个都在 .NET 生态里有一定群众基础。

方案本质数据存储形态适合的应用类型许可证
SQLite + EF CoreC语言编写的嵌入式关系库单个 .db/.sqlite 文件通用业务系统、分析型工具公有领域
SQLite + Microsoft.Data.Sqlite同上的轻量驱动层同上简单数据访问、迁移过渡公有领域
LiteDB纯 C# 实现的 NoSQL 文档库单个 .db 文件配置存储、原型、中小型应用MIT
VistaDB纯 C# 实现的关系库单个 .vdb5 文件WinForms / WPF 传统桌面应用商业授权
SQL Server Express LocalDB微软官方的轻量 SQL Server文件型数据库,由服务进程管理开发环境、小型生产免费
RavenDB Embedded文档库的嵌入式模式文件夹形式需要全文检索、复杂查询的场景商业/社区版

我实际测过其中四个,LiteDB 和 VistaDB 是近期专门搭了 Demo 跑过的,RavenDB Embedded 我只在文档层面研究过,没有纳入最终横评,原因是它对于纯本地单文件需求来说还是偏重。LocalDB 严格讲不算嵌入式,它有一个后台服务在管文件,但它和 Visual Studio 集成得非常好,单独拎出来讲有它的价值。

2.2 选型坐标:四个维度定生死

我在选型时不会先看性能报表,那是最后一步。我自己的评估顺序很固定,总共四个维度,每一个都会直接影响项目后续的开发和运维体验。

第一是部署复杂度。桌面应用最怕什么?最怕用户装完程序跑不起来。SQLite 在 .NET 里用 Microsoft.Data.Sqlite 驱动时,Windows 下需要带上 e_sqlite3.dll 原生库,Linux 下需要 libsqlite3,macOS 下类似。这个 dll 如果版本和运行时对不上,启动就会报 DllNotFoundException。LiteDB 和 VistaDB 没有这个问题,因为它们就是纯托管代码,一个 NuGet 包搞定,没有原生依赖,这点在 .NET MAUI 场景里简直是救命稻草。

第二是数据模型贴合度。如果你的数据是强关系型的,比如订单、明细、账务这种,需要 join、事务、外键约束,那关系库是正路,SQLite 和 VistaDB 都在第一梯队。如果你的数据更像文档,比如配置项、JSON 快照、日志记录,字段结构经常变,LiteDB 用 BsonDocument 存取会舒服得多,它内部就是文档模型,加字段不用改表结构。

第三是并发模型。这里的并发主要指多进程并发。SQLite 默认的并发策略是文件锁,一个进程写的时候,另一个进程的写入会等到超时或者直接报 database is locked。LiteDB 在并发上也类似,它有一个不成熟的多线程处理机制,但同一时刻只能有一个写事务。VistaDB 对多用户并发支持好些,行级锁做得比较到位。如果你的应用只有一个进程,并发问题基本不用太担心;如果是多进程,SQLite 需要开 WAL 模式并且合理设置 busy_timeout。

第四是生态和迁移成本。EF Core 是目前 .NET 数据访问的事实标准,SQLite 有最完善的 EF Core 支持,包括迁移(migration)、LINQ 查询翻译、事务等。LiteDB 有两个版本的 API,老的 LiteDB 4.x 和新出的 LiteDB 5.x,API 改动很大,网上一些老教程会误导人。VistaDB 也支持 EF Core 6/8,但它属于商业产品,网上社区资料相对少。LocalDB 用的是标准 SQL Server 的 T-SQL 方言,如果你的团队本来就熟 SQL Server,迁移到 LocalDB 几乎没有学习成本,但这货没法用于生产环境多用户部署。

2.3 为什么我不直接无脑推荐 SQLite

我承认 SQLite 是这个领域的人气王,社区活跃度最高,Stack Overflow 上的问题几乎都能搜到答案。但我恰好在实际项目中遇到几个问题,让我对它保持一份清醒。

第一个问题是类型映射。EF Core 里 SQLite 对 decimal 的处理有点特殊,默认情况下 decimal 在 SQLite 里会被映射为 TEXT 存储(EF Core 的 SQLite provider 为了保持精度),这导致老数据库里如果存的 decimal 字段格式不规范,查询出来反序列化时可能出现精度丢失。举个我踩过的例子:项目里有个金额字段,用了 decimal,存进去是 "12.3400",EF Core 读出来时会按当前线程的区域设置解析,如果用户的系统区域设置是德语区(小数点用逗号),那读取就会直接抛 FormatException。

第二个问题是跨平台原生库的坑。我做过一个 WPF 工具的自动更新,发布的时候一切正常,结果有用户反馈装完打不开。查了半天是杀毒软件把 e_sqlite3.dll 当可疑文件隔离了,因为那个 dll 是原生代码,没有微软签名。后来我改成用 SQLitePCLRaw.bundle_e_sqlite3 包并走 provider 初始化,才稳定下来。

第三个问题是并发控制需要一点经验。单机应用如果只开一个进程,SQLite 很稳,可你要是做一个托盘工具,把数据库服务和 UI 拆成两个进程,那就必须精调 WAL 模式、busy_timeout、连接池。我见过有人直接抄网上的连接字符串,也不看 journal_mode,跑几天就出现 database is locked,然后怪 SQLite 不靠谱。

所以结论不是“SQLite 不行”,而是“SQLite 需要你用对姿势”。它仍然是通用关系型本地存储的最佳默认选项之一,但你必须知道它的边界在哪里。

3. 核心技术点拆解:每个方案的看家本领与致命短板

3.1 SQLite 在 .NET 中的正确打开方式

SQLite 在 .NET 中接入的标配组合是 Microsoft.Data.Sqlite + EF Core。如果你只做简单的键值存储,不想引入 EF Core 那一套,直接只用 Microsoft.Data.Sqlite 就够了,它的 API 风格和 ADO.NET 很像,上手非常快。

如果你走 EF Core 路线,有几个细节必须注意。连接字符串里有一项很重要:

Data Source=appdata.db;Cache=Shared;Foreign Keys=True;Pooling=True
  • Cache=Shared表示同一个进程内多个连接共享同一个 SQLite 缓存,减少文件锁冲突。
  • Foreign Keys=True必须在连接字符串或连接初始化时开启,SQLite 默认不检查外键约束,很多新手在这里翻车。
  • Pooling=True从 Microsoft.Data.Sqlite 6.0 开始支持,开启后连接复用,减少打开关闭文件的开销。

还有一个必须养成的习惯:启用 WAL 模式。在 EF Core 的OnConfiguring或者首次连接打开后执行:

using var connection = new SqliteConnection("Data Source=appdata.db"); connection.Open(); using var command = connection.CreateCommand(); command.CommandText = "PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;"; command.ExecuteNonQuery();

WAL 模式最大的好处是读操作不阻塞写操作,写操作也不阻塞读操作,对桌面应用来说体感很关键。busy_timeout=5000是指在遇到文件锁时最多等待 5 秒,超过就报错,你可以根据场景调大调小。

我自己实测过一个有点反直觉的现象:SQLite 在单文件大批量写入时,如果禁用事务逐条 insert,耗时可能是启用事务的 10 倍以上。所以写批量数据的代码我永远长这样:

using var transaction = await context.Database.BeginTransactionAsync(); try { foreach (var item in items) { context.Items.Add(item); } await context.SaveChangesAsync(); await transaction.CommitAsync(); } catch { await transaction.RollbackAsync(); throw; }

3.2 LiteDB:纯 C# 文档库的甜与痛

LiteDB 是我最近玩得比较多的方案,因为它解决了 SQLite 在部署上的一个痛点:无原生依赖。全部 C# 实现,发布目录只有托管 dll 和一个数据文件,这在 .NET MAUI 和 WinForms 里非常省心。

它的 API 设计最接近 MongoDB 的体验。比如定义实体:

public class User { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } }

插入和查询:

using var db = new LiteDatabase(@"users.db"); var col = db.GetCollection<User>("users"); col.Insert(new User { Name = "张三", Email = "zhangsan@example.com" }); var user = col.FindOne(x => x.Email == "zhangsan@example.com");

如果你存的是纯文档类数据(配置、日志、快照、规则引擎的条件表达式等),LiteDB 真的很爽,字段结构变化不需要迁移脚本,直接把新的 BsonDocument 塞进去就行。

但它有几个坑,我提前给你踩了。第一是 5.x 版本的 API 变化巨大,网上大量教程基于 4.x,LiteDatabase的构造函数参数、Include的写法都有变化,照着老教程写你会编译不过去。第二是并发能力一般,官方文档明确说支持多线程读,但单实例写事务在同一时间只能有一个,多进程同时打开同一个文件是不推荐的。第三是可恢复性弱,如果程序在写过程中崩溃,文件损坏的风险比 SQLite 高,所以一定要定期备份数据文件,写入频率高的场景要小心。

3.3 VistaDB:老牌商业方案还值不值得选

VistaDB 是 .NET 圈子里的老面孔了,从 .NET Framework 时代就存在,纯 C# 实现、单文件、支持行级锁,对传统关系的支持比 LiteDB 完整得多。它的一个招牌特性是支持加密数据库,直接就内建了加密算法,而 SQLite 要加密要么用 SEE(收费),要么用 SQLCipher(开源但编译麻烦),VistaDB 在商业授权里直接把这个能力打包了。

我实际用它跑过一个库存管理 Demo,和 EF Core 的集成走的是正规 provider 流程,迁移命令dotnet ef migrations add也能正常跑,事务、外键、存储过程(是的,它支持类存储过程的语法)都齐全。如果你的应用是传统 MIS 系统,比如进销存、财务、固定资产管理,数据结构强关联、对数据安全有合规要求,VistaDB 是一个下限很高的选择。

缺点也很明显:商业授权费对个人开发者和预算敏感的小团队是一道坎;社区资料数量完全没法跟 SQLite 比,你遇到一个细节问题可能搜遍全网也找不到答案;它在 Linux/macOS 上的支持虽然官方说可以跑,但主要场景还是 Windows 桌面。

3.4 LocalDB:被忽视的“半个嵌入”选项

LocalDB 是微软官方提供的一个轻量 SQL Server 实例,它不是进程内嵌入式库,而是由一个后台服务按需启动的独立进程。它最大的价值在于:和 SQL Server 完全兼容的 T-SQL,以及 Visual Studio 原生工具链的无缝衔接。

如果你做一个桌面工具,目标用户是内部员工,且机器上可能装了 SQL Server Management Studio,那么 LocalDB 非常合适。你直接用(localdb)\MSSQLLocalDB作为服务器地址,连接字符串和 SQL Server 几乎一样:

Server=(localdb)\MSSQLLocalDB;Database=AppData;Trusted_Connection=True;

但 LocalDB 有个硬伤:它本质上是开发友好型实例,不适合分发给最终用户。首先,目标机器必须装有 SQL Server LocalDB 运行时(几十 MB 的安装包),其次,同一个实例能同时打开的数据库数有限制,另外 LocalDB 的实例默认不会开机自启,第一次连接时会有几百毫秒的启动延迟。这些对于正式的桌面客户端分发场景都是减分项。

4. 实操指南:一套完整的数据访问层设计方案

选型不能停留在表格对比上,我把我最近一个 WPF 项目里实际跑通的数据访问层设计方案拿出来,你照着改就能用。这个方案虽然用的是 SQLite + EF Core,但整个设计思路对其他几个库同样适用。

4.1 项目结构规划

我习惯把数据访问层独立成一个类库项目,不掺和 UI 逻辑。目录结构大致如下:

src/ App.Domain/ // 实体类、枚举、接口 App.Infrastructure/ // EF Core 上下文、仓储实现、迁移 App.Wpf/ // 视图层

Domain 层只放 POCO 实体,不引用任何 EF Core 的 dll。Infrastructure 层才引入 EF Core 相关包。这样做的目的是保持实体纯净,以后要是换存储方案,不会牵连 UI 层改动。

4.2 DbContext 配置要点

我的AppDbContext长这样:

public class AppDbContext : DbContext { public DbSet<User> Users { get; set; } public DbSet<Order> Orders { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var connectionString = new SqliteConnectionStringBuilder { DataSource = Path.Combine(AppContext.BaseDirectory, "appdata.db"), Cache = SqliteCacheMode.Shared, Pooling = true, ForeignKeys = true }.ToString(); optionsBuilder.UseSqlite(connectionString); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<User>(entity => { entity.ToTable("users"); entity.HasKey(x => x.Id); entity.Property(x => x.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP"); }); } }

注意几个细节:DataSource我用的是AppContext.BaseDirectory,也就是程序运行目录。有人喜欢用Environment.SpecialFolder.ApplicationData,这样数据库文件放在用户目录下,升级程序时不会被覆盖,多用户共用一台电脑时数据能隔离。取舍取决于你的产品形态。

还有一个容易被忽略的点:SQLite 的DateTime默认存成 TEXT 格式(形如2024-06-19 14:30:00),如果你希望统一成 ISO 8601 带时区,需要在连接字符串里加DateTimeFormat=ISO8601。

4.3 自动迁移的开机流程

桌面应用最忌讳在用户电脑上手动跑迁移命令。我启动流程里加了一段自动执行Database.Migrate()的逻辑:

public static void InitializeDatabase() { using var context = new AppDbContext(); context.Database.Migrate(); }

Migrate()会自动检查数据库文件是否存在,不存在则创建,然后按迁移历史把架构建好。首次启动时会多花一两秒钟,之后都是毫秒级。

但这里有个两种场景要分开:如果你的数据库文件是程序自己创建的,直接Migrate()没问题;如果是从旧版本程序升级上来的,就要小心Migrate()和已有数据的兼容性。我遇到过一次字段类型变更导致迁移失败,后来在迁移代码里手动写了数据转换的 SQL 才搞定。升级场景最好先备份旧库,然后跑迁移,再验证数据量。

4.4 类型安全的仓储封装

我不主张每个实体都写一套仓储,但我会封装一个通用的IRepository<T>,内部只做最基本的事,复杂查询直接走IQueryable交给上层组合。

public interface IRepository<T> where T : class { Task<T?> GetByIdAsync(int id); Task<IEnumerable<T>> GetAllAsync(Expression<Func<T, bool>>? predicate = null); Task AddAsync(T entity); Task UpdateAsync(T entity); Task DeleteAsync(T entity); Task<int> SaveChangesAsync(); }

实现里就包一下DbSet<T>和DbContext.SaveChangesAsync,不做花活。真正的复杂查询用 LINQ 在 UI 层直接写,配合 AsNoTracking 提高只读性能。

4.5 连接生命周期管理

EF Core 的 DbContext 是工作单元模式,桌面应用里建议遵循“一个请求一个上下文”的思路。WPF 的 ViewModel 在构造函数里注入一个上下文工厂,比如:

public class UserListViewModel { private readonly IDbContextFactory<AppDbContext> _factory; public UserListViewModel(IDbContextFactory<AppDbContext> factory) { _factory = factory; } public async Task LoadUsersAsync() { await using var context = await _factory.CreateDbContextAsync(); var users = await context.Users.AsNoTracking().ToListAsync(); // 绑定 UI 数据 } }

用IDbContextFactory而不是直接注入 DbContext,可以避免跨操作共享同一个上下文导致的缓存陈旧、线程安全问题。注册服务时记得:

services.AddDbContextFactory<AppDbContext>(options => options.UseSqlite(...));

5. 常见问题与排查技巧实录

5.1 数据库文件被占用,无法删除或复制

这个坑太经典了。WPF 程序在调试时经常崩溃,调试器还没有完全释放资源,然后你想删掉数据库文件重新来,结果提示“文件正在被另一个进程使用”。

排查思路按顺序来:先确认程序是否真的退出了(看任务管理器的进程列表);然后确认是否有数据库连接没有释放,比如用了using但没包await using、连接池里的连接还挂着;最后检查是否有后台任务没取消。如果只想快速解决,可以在程序退出时执行SqliteConnection.ClearAllPools()(如果是 Litedb 就确保Dispose()了实例)。

5.2 database is locked 的诱因与解法

这个几乎每个用 SQLite 的人都会遇到。常见的触发场景是:多个连接同时在写、一个写事务还没结束另一个连接又尝试写、长事务卡住了文件锁。

我的标准做法是三步:

  1. 开启 WAL 模式并把 busy_timeout 设到 3000~5000 毫秒。
  2. 写操作统一走同一个连接串,并且尽量集中批次操作,不要一条一条开连接。
  3. 配置连接池,确保池内连接数合理(默认是 5 到 10),不要自己 new 一堆连接。

还有一种隐蔽情况:EF Core 的一个SaveChangesAsync走了异步,但你在事务里同步调用另一个查询,由于 SQLite 的锁机制是写事务持有独占锁,这个同步查询会被卡住直到超时。解决办法是把所有操作都改成异步链。

5.3 中文乱码怎么解决

数据存取没有问题,但读取回来显示乱码,十有八九是数据库文件的编码与你程序的期望不一致。SQLite 默认以 UTF-8 编码存储文本,Microsoft.Data.Sqlite 默认也是 UTF-8,正常情况下不应该乱码。

如果你是从老版本 .NET Framework 项目迁移过来的,老库文件可能是 UTF-16(Unicode)编码,这时需要在连接字符串里显式设置:

Data Source=legacy.db;DefaultUtf16=True;

再不行就写一段读取脚本,把文本数据先读成字节,再用Encoding.Unicode.GetString()转出来,重新写回 UTF-8。

5.4 文件损坏怎么办

SQLite 的容错性已经不错了,但任何数据库都挡不住断电、杀毒软件干预、磁盘异常。我的安全阀是三个地方同时上:一是定期把数据文件复制到备份目录,频率根据写入量定;二是关键写入操作不要怕麻烦,开启事务并设置 WAL;三是提早在代码里捕获SQLiteException,提示用户数据库损坏并引导恢复备份,总比直接闪退体面。

LiteDB 用户要更谨慎一点,它的崩溃恢复机制没有 SQLite 成熟,我建议在每次写操作密集的任务完成后立即 flush 并关闭数据库,而不是长期保持打开。

5.5 性能从多少毫秒优化到多少毫秒

最后分享一组我在同一台开发机上跑出来的实际数字,数据量 10 万条:

场景SQLiteLiteDBVistaDB
全表扫描(无索引)210ms320ms280ms
主键单条查询1ms2ms1ms
批量插入 5000 条(单事务)680ms550ms720ms
数据库文件大小8.6MB12.4MB9.3MB

批量插入 LiteDB 最快,全表扫描 SQLite 最快,主键查询其实都很快。单机桌面应用根本打不到性能瓶颈,别把性能当作主要选型依据,部署复杂度和数据模型贴合度才是优先考虑的。

6. 选型决策路径:从需求到方案的映射方法论

我把这几周折腾下来的一套判断方法整理成可复用的步骤,不管你是新项目还是老项目迁移,都可以按这个顺序走。

6.1 第一步:先分清你的数据是“关系型”还是“文档型”

这个分法决定了你是选 SQLite/VistaDB 还是 LiteDB。关系型数据的特点是有明确的实体关联,比如订单有明细、用户有角色、账套有科目,你需要 join 查询、外键约束、事务一致性保证。文档型数据的特点是结构松散、字段动态、多为配置文件、缓存快照。

判断标准很简单:你写查询的时候,是“这个用户关联了哪些订单”这种关系思维多,还是“取这个配置项的内容”这种直取思维多。前者用关系库,后者用文档库。有人非要用 LiteDB 存强关系数据,也不是不行,但联接查询写起来很别扭,最后大概率会后悔。

6.2 第二步:盘点你的部署环境与发布方式

  • 如果是纯 Windows 桌面(WinForms/WPF),三个主流方案都可以选,这时看团队熟悉度和是否需要加密。
  • 如果是 .NET MAUI 跨平台(Android/iOS/Windows),强烈建议优先考虑纯托管方案的 LiteDB,原生依赖少,打包链路最简单。
  • 如果目标是 Linux 服务器上的单机服务,SQLite 是最稳的选择。
  • 如果目标用户是企业内部网且已经依赖 SQL Server 生态,LocalDB 可能是最平滑的过渡方案。

6.3 第三步:评估数据安全和恢复要求

这里有个容易被人忽视的地方。SQLite 本身没有内建加密,你想对数据库文件加密,要么用 SQLCipher(社区版要自己编译,商业授权要钱),要么在应用层做字段加密。VistaDB 内建加密,对数据敏感型传统 MIS 系统是加分项。LiteDB 从 5.x 开始支持ConnectionString里的Password参数,但加密强度比较基础,只能挡住文件被直接拷贝打开的情况,防不了专业取证。

如果你的应用承载的是用户核心资产(比如财务数据),我建议不管选谁,都在应用层叠加 AES 对称加密字段加密,不要把宝全押在数据库的加密机制上。

6.4 第四步:看你和团队的查询语言偏好

团队如果都是老 SQL 手子,上来就能写 join、group by、having,SQLite 几乎没有学习成本。如果团队更习惯 LINQ 一把梭,LiteDB 的 fluent API 也够舒服。如果你对 SQL Server 的 T-SQL 方言很熟,直接上 LocalDB 可以免学习成本。选型的时候不要高估团队的适应能力,一个全组都熟的方案,比一个网上口碑更好但没人用过的高冷方案靠谱得多。

7. 迁移实战:从 SQLite 迁到 LiteDB 的踩坑记录

选型这件事有时候不是一次能定死的。项目做了一半,发现数据模型越来越像文档,之前的强关系设计根本用不上几个 join,于是我把一个模块从 SQLite 迁到了 LiteDB。过程比想象中麻烦,把值得注意的点写下来。

7.1 实体模型的变化处理

SQLite 里我原来是这样的一张表:

CREATE TABLE configs ( id INTEGER PRIMARY KEY AUTOINCREMENT, [key] TEXT NOT NULL UNIQUE, [value] TEXT NULL, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP );

迁移到 LiteDB 之后,我面临一个问题:LiteDB 的Id局限性很强。默认情况下,如果你用int Id,插入时如果Id为 0,它会自动赋自增值;如果是 string Id,就需要用Id = Guid.NewGuid().ToString()。我的配置项没有严格的自增需求,最终用了 string 类型:

public class ConfigItem { public string Id { get; set; } = Guid.NewGuid().ToString(); public string Key { get; set; } public string Value { get; set; } public DateTime UpdatedAt { get; set; } }

这里有个迁移时的坑:SQLite 的自增 id 在语义上常常用于关联外键,如果你把表迁移到 LiteDB 后想保留 id,需要把原来的整型 id 绑定成实体的 Id 属性。LiteDB 只识别Id属性名(或标记了[BsonId]的属性),不要试图保留原来的id字段同时另建主键,那只会给自己找不自在。

7.2 数据导出导入脚本

我写了一个控制台工具做数据迁移,逻辑很简单:从 SQLite 读出来,再插到 LiteDB,全程开启事务,分批插入。这里有个性能经验:LiteDB 批量插入时,一次插几千条和一次插一条,性能差一个数量级,所以务必用InsertBulk。

using var db = new LiteDatabase(new ConnectionString { Filename = "new.db" }); var col = db.GetCollection<ConfigItem>("configs"); var items = ReadFromSqlite(); // 从旧库读取 col.InsertBulk(items);

7.3 查询语法的重写

这个是最花时间的。原来 EF Core 里用 LINQ 写:

var value = await context.Configs .Where(c => c.Key == key) .Select(c => c.Value) .FirstOrDefaultAsync();

LiteDB 里对应的写法是:

var config = col.FindOne(c => c.Key == key); string? value = config?.Value;

看起来区别不大,但一旦查询带排序、分页、聚合,两者的表达式语义还是有差距。比如分组统计,LiteDB 需要用col.Query().GroupBy(...)配合聚合表达式,没有 EF Core 的 LINQ 翻译器帮你兜底。迁移前务必把所有查询梳理一遍,分类标注哪些是简单查询、哪些是复杂查询,心理预期要放平。

7.4 迁移后的体检清单

迁移完不能直接删旧库,我会按这个清单做体检:

  • 数据条数一致(新旧库各跑一遍 count)。
  • 抽样比对每条记录的字段值(写个 diff 工具)。
  • 跑一遍原有自动化测试,重点看日期格式、浮点数精度。
  • 测试异常关闭后能否重新打开,观察文件大小是否异常增长。

我这次迁移最后发现一个问题:SQLite 里存的日期是"2024-06-19 14:30:00"这种 TEXT 格式,LiteDB 里自动转成了DateTime类型,读取正常,但有一个老数据的时间是空字符串,LiteDB 反序列化时直接丢了一个异常。最后在读取函数里加了一个兜底判断才搞定。老数据的脏值问题,永远比你想的多。

8. 一点真实感受

搞完这一轮选型和迁移,我有个挺深的体会:本地数据库选型没有银弹,每个方案都是带着它的“性格”来的。SQLite 像一匹血统纯正但需要驯服的马,能力强、生态好,可你不管好原生依赖和并发模式,它随时给你撂挑子。LiteDB 像个灵巧的年轻人,部署省心、API 亲切,但在数据安全上你需要额外操心。VistaDB 像稳重的中年人,功能齐全、自带安全能力,但你需要为这份稳定付费。

最实用的建议就一句:先画清楚你的数据模型和部署目标,再选方案,别本末倒置。如果实在拿不定主意,先用 SQLite 起步,因为它是这几个方案里最容易被替换掉的——EF Core 的抽象层让你后续迁移到其他关系库的成本相对可控。但如果你同时预感到数据模型会越来越“文档化”,那从一开始就用 LiteDB 反而能省一次迁移。

最后分享一个我常用的判断技巧:选型时做一个 1 小时的原型,不是写 hello world,而是把你项目里最复杂的那条查询原样写一遍,再模拟一次批量写入加备份恢复。哪个方案在这个原型里让你皱眉最少,往往就是那个陪你走完整条路的选择。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询