C#上位机集成SQLite3:增删改查Demo与五大避坑指南

发布时间:2026/10/11 4:38:52
C#上位机集成SQLite3:增删改查Demo与五大避坑指南 简介面向C#开发者的SQLite3增删改查Demo适合刚接触桌面或移动端本地数据库的初学者。资源围绕System.Data.SQLite展开涵盖连接数据库、建表、插入、查询、更新、删除以及参数化查询防SQL注入、事务提交/回滚等关键操作同时将常用逻辑封装为SqliteOpera辅助类的静态方法每个方法都处理好连接创建、命令执行、异常捕获与资源释放便于理解面向对象封装思想。资源包共175个文件、约32.56MB核心为9个cs源码文件另有40个dll运行库、6个nupkg依赖包、6个exe工具、14个targets构建配置及1个db示例数据库sln/csproj齐全可直接用Visual Studio打开运行。已有2256人学习下载。读者可提取辅助方法快速集成到自己项目中省去从零封装ADO.NET的流程还能通过db文件直观验证数据变化是掌握C#操作SQLite3的实用参考。1. 这个 Demo 在解决什么问题C# 上位机和桌面工具为什么需要 SQLite3做上位机程序或者内部工具的人迟早会被一个问题卡住采集到的扭矩值、配置参数和操作记录往哪存装 SQL Server 太重用 Access 又躲不开 32 位 Office 的兼容雷。SQLite3 是嵌入式关系型数据库单文件、零配置C# 通过 ADO.NET 驱动直接调用发布时多一个 .db 文件就算完成数据落盘。这也是数据库增删改查这个经典需求在桌面端最常见的解法。这篇以一个完整的 C# SQLite3 增删改查 Demo 为主线从驱动选型和建表初始化到增删改查的参数化写法再展开实际项目里最容易翻车的锁冲突、架构不匹配和相对路径问题。新手照着敲能跑通熟手可以直接跳到第 5 章看避坑。2. 动工前先定选型System.Data.SQLite 与 Microsoft.Data.Sqlite 怎么选2.1 两个驱动包出身、目标框架和原生依赖的差别C# 里接 SQLite公开资料里最常见的是两个驱动。System.Data.SQLite 是 SQLite 生态里老牌的 ADO.NET 提供程序从 .NET Framework 2.0 时代就开始用了NuGet 包名叫 System.Data.SQLite。它自带一份非托管的 SQLite.Interop.dll通过 P/Invoke 调原生库因此历史上有 x86/x64 两份原生文件的说法。Microsoft.Data.Sqlite 则是微软面向 .NET Core / .NET 5 推出的轻量驱动包很小API 风格更贴近现代的 ADO.NET底层通过 SQLitePCLRaw 把原生库包进去部署时不用自己操心生原文件。选型的判断标准我的习惯是看项目的目标框架和部署方式而不是看哪边文档多。下表把两边最关键的差异列出来对比项System.Data.SQLiteMicrosoft.Data.SqliteNuGet 包名System.Data.SQLiteMicrosoft.Data.Sqlite适用框架.NET Framework 4.x 老项目为主.NET Core / .NET 6 新项目原生库自带 SQLite.Interop.dll分 x86/x64通过 SQLitePCLRaw 内置跨平台API 风格传统 ADO.NET功能全精简几乎只覆盖 ADO.NET 标准离线更新 DataTable支持 SQLiteDataAdapter 的 Update不提供适配器绑定后要自己写更新加密扩展官方基础包除外需用带加密的变体默认无加密可替换 SQLitePCLRaw 的加密 bundle如果你手里是一个 WinForms 加 .NET Framework 4.8 的老项目我一般直接用 System.Data.SQLite因为它的 DataAdapter 能把 DataGridView 的离线编辑回写数据库老代码改动最小。如果是新开的控制台、WPF 或 Web API 项目只要目标框架在 .NET 6 以上我推荐 Microsoft.Data.Sqlite包体干净跨平台部署省心。后面的代码示例也统一用这个驱动。两边在 SQL 语法层面完全一样锁、WAL、busy_timeout 这类行为由 SQLite 核心决定不会因为驱动不同而变化。真正会变的是连接串里的参数名、参数前缀风格和异常消息细节。所以先把选型定下来后面的坑才有的放矢。2.2 NuGet 安装三步装好顺带解决原生库架构安装驱动的方式在 Visual Studio 里打开“管理 NuGet 程序包”搜索 Microsoft.Data.Sqlite装到目标项目即可。.NET CLI 环境下安装dotnet add package Microsoft.Data.Sqlite装完就能在代码里看到 Microsoft.Data.Sqlite 命名空间。有一点提前说NuGet 会按目标框架解析依赖net48 和 net8.0 项目装的是同一份包但传递依赖完全不同别把 net8.0 项目里装的包拿到 net48 项目里硬编编译能过运行时报什么都说不准。System.Data.SQLite 的安装要留意原生库。这个包的输出目录里会带 x64 和 x86 两个子目录里面各有一份 SQLite.Interop.dll。项目平台目标是 AnyCPU 时编译器会把两个子目录都复制到输出目录运行时按进程位数挑一个加载。一旦你的平台目标设成 x86却在 64 位系统上跑或者反过来最常见的后果是启动时抛 BadImageFormatException。我踩过一次Debug 下 AnyCPU 跑得好好的发布时为了兼容一个老采集卡把平台改成 x64结果忘了重新复制 Interop.dll双击就崩。后面第 5 章把这个坑单独展开。Microsoft.Data.Sqlite 没有这个烦恼原生库放在 NuGet 的 runtimes 目录里按 RID 组织dotnet publish 时自动带目标系统对应的那一个。所以如果你不想跟原生 dll 较劲新项目直接选它。2.3 连接串的 5 个参数Data Source 之外还有谁在起作用SQLite 的连接串没有 SQL Server 那么复杂但也不是只有 Data Source 一个。最常用的一套写法看起来是这样的Data Sourceappdata.db;ModeReadWriteCreate;CacheShared;Default Timeout30;PoolingTrue路径里带空格或中文时直接拼字符串容易错我习惯用 SqliteConnectionStringBuilder 来构造var builder new SqliteConnectionStringBuilder { DataSource dbPath, Mode SqliteOpenMode.ReadWriteCreate, Cache SqliteCacheMode.Shared, DefaultTimeout 30, Pooling true }; var conn new SqliteConnection(builder.ToString());这 5 个参数是 Demo 阶段就值得弄明白的参数默认值作用你什么时候要改它Data Source无数据库文件路径永远必须填建议直接上绝对路径ModeReadWriteCreate打开方式读写且不存在就创建只读报表场景改 ReadOnly防误写CacheDefault连接缓存模式Shared 表示进程内共享缓存同一进程多连接读写时才需要 SharedDefault Timeout30ADO.NET 命令默认超时单位秒批量写入慢时观察它但锁超时另有参数Poolingtrue是否启用连接池长时间占用连接时建议 false减少池内状态残留注意 Default Timeout 管的是 ADO.NET 命令执行等待跟 busy_timeout 是两回事。SQLite 的锁等待超时由 PRAGMA busy_timeout 控制不会因为你把 Default Timeout 设成 5锁等待就只等 5 秒。这两个超时的关系是命令执行超过 Default Timeout 抛错写操作等锁超过 busy_timeout 抛错谁先到算谁。Demo 阶段先按默认值跑通锁问题到第 5 章统一处理。3. 建库建表与连接管理让 Demo 第一次 F5 就跑起来3.1 打开连接即建库First Run 自动初始化SQLite 不需要你先用某个工具建好库再让程序去连。连接串的 Mode 用 ReadWriteCreateOpen() 时发现目标文件不存在会自动创建空文件。建表语句用 CREATE TABLE IF NOT EXISTS 兜底这样程序每次启动跑一遍初始化代码也不会报错。我认为这是最稳的初始化方式没有之一。using var conn new SqliteConnection(Data Sourceappdata.db); conn.Open(); var initSql CREATE TABLE IF NOT EXISTS device_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT NOT NULL, torque_value REAL, record_time TEXT NOT NULL DEFAULT (datetime(now,localtime)) );; using var cmd conn.CreateCommand(); cmd.CommandText initSql; cmd.ExecuteNonQuery();这段代码说明三点。第一id 列用 INTEGER PRIMARY KEY AUTOINCREMENT跟直接写 INTEGER PRIMARY KEY 的区别在于AUTOINCREMENT 保证 id 不会复用已删除的最大 rowid日志表一般不在乎 id 是否连续但在 id 要返回给业务层做后续关联时复用的 id 会在日志里造成“新旧数据同号”的误导。第二record_time 用 TEXT 存 datetime(now,localtime) 的结果这是 SQLite 社区的常见约定——时间就是字符串可直接比较可排序不用为了省空间拆成二进制存。第三初始化代码的位置控制台程序放在 Main 最开头WinForms 放在 Form_Load 里只要保证它先于所有增删改查语句执行就行。这里有个容易忽略的细节datetime(now,localtime) 是按运行机器的本地时区算的换时区后历史数据不会跟着变。如果你做的是跨时区部署的采集工具更稳的做法是统一存 UTC显示时再转本地时间避免不同机器写入的时间口径不一致。3.2 用 sqlite3 命令行验证先把“库本身”和“C# 代码”分开我调试 SQLite 相关代码时有个习惯遇到诡异问题先用 sqlite3 命令行直接对同一个 .db 文件执行同样的 SQL。这一步不是为了炫技而是为了做二分定位——如果是 SQL 写错了命令行里同样报错问题在 SQL如果命令行正常只有 C# 里出错问题在驱动或连接方式。sqlite3.exe 是 SQLite 官方发布的命令行工具。Windows 下把它解压到任意目录放进 PATH就能在终端里直接使用。验证刚才的建表sqlite3 appdata.db .headers on .mode column .tables .schema device_log前两条 .headers 和 .mode 是控制输出格式让列名显示成表头、列宽对齐查数据时肉眼过一遍比默认管道式输出清楚得多。.tables 列出库里的表确认建表语句真的执行成功.schema device_log 把建表语句原样打印出来检查有没有写错。再顺手插一条测试数据看看INSERT INTO device_log (device_id, torque_value) VALUES (DEV-TEST, 60.5); SELECT id, device_id, torque_value, record_time FROM device_log;这一步结束你对“这个文件是不是合法数据库、表结构是不是我想要的”就有了确定答案接下来排查 C# 代码时可以完全排除对库本身的怀疑。以后谁说“数据没了”先看文件在不在、再看表在不在别急着改代码。3.3 连接生命周期短连接、长连接还是全局单例SQLite 打开一个连接的成本比 SQL Server 低很多因为不存在网络握手背后就是打开一个文件。因此 Demo 阶段最简单的做法是每个数据库操作都 new 一个连接、用 using 块包住、用完就 Dispose。但这里面有个度上位机通过串口或网口收数据时一秒可能来几十条写操作每条都 Open/Close 一次开销虽然不高但配合连接池和事务性能差距会非常明显。我的常见做法是写一个静态辅助方法public static SqliteConnection OpenAppDb() { var conn new SqliteConnection(Data Sourceappdata.db); conn.Open(); return conn; }调用方这样用连接的生命周期随方法走using (var conn OpenAppDb()) using (var cmd conn.CreateCommand()) { cmd.CommandText SELECT count(*) FROM device_log;; var count cmd.ExecuteScalar(); }有两件事我不会做。第一不会把连接对象存成静态字段跨线程共享WinForms 的按钮事件和后台线程同时抢一个连接即使 SQLite 原生线程模式能处理底层并发ADO.NET 层的状态也可能乱典型表现是莫名其妙的“连接忙”异常。第二不会手动调 conn.Close() 而又不用 DisposeClose 之后连接可能回到池里池内连接残留的事务和 PRAGMA 状态是你之后排查不出来的隐患。对于 Microsoft.Data.SqlitePooling 默认开启Dispose 连接不是真关物理连接而是归还池子。要完全绕开连接池在连接串里加 PoolingFalse。桌面工具里的另一个隐患是 UI 线程被 SQLite 操作卡住数据库文件在机械硬盘或网络盘上时Open 和批量写可能耗时几十毫秒WinForms 界面就会明显卡顿。Microsoft.Data.Sqlite 提供了 OpenAsync 和 ExecuteNonQueryAsync耗时操作换成 async 版本能保证界面不冻结。但一个连接同一时刻只能有一个异步操作在跑别在两个 await 之间复用同一个 cmd。这个点 Demo 阶段可以先不管模块化阶段必须处理。4. 增删改查核心实现参数化、自增主键与事务的配合4.1 插入记录参数化写法与拿回自增 ID这是整个 Demo 里最重要的一段代码先给结论所有输入值一律用参数传进去不拼字符串。原因不只是防注入——虽然所有安全资料都会提注入——对 SQLite 来说更现实的问题是字符串拼接会引入引号转义灾难device_id 里出现一个单引号INSERT 语句就直接语法错误。还有浮点数torque_value 拼成 58.6 再转字符串精度和类型都不可控。参数化把这些一次性解决。using var conn OpenAppDb(); using var tx conn.BeginTransaction(); using var cmd conn.CreateCommand(); cmd.Transaction tx; cmd.CommandText INSERT INTO device_log (device_id, torque_value) VALUES ($devId, $torque);; cmd.Parameters.AddWithValue($devId, DEV-002); cmd.Parameters.AddWithValue($torque, torqueValue); cmd.ExecuteNonQuery(); cmd.Parameters.Clear(); cmd.CommandText SELECT last_insert_rowid();; var newId Convert.ToInt64(cmd.ExecuteScalar()); tx.Commit();这段代码里有三个细节值得记。一是 Microsoft.Data.Sqlite 的参数名前缀推荐用 $System.Data.SQLite 用 也认两个驱动都兼容但同一个语句里不要混用。二是 last_insert_rowid() 必须在同一个连接、同一个事务内调用换一个连接拿到的一定是 0 或者别的连接插入的 id如果你发现自增 ID 拿不对先检查是不是重新打开了连接。三是 ExecuteNonQuery 之后复用同一个 cmd 对象时先 Parameters.Clear() 再改 CommandText否则旧参数残留在对象里新语句执行时会因为参数未使用而报错这一条是新手翻车的高发点。还需要注意AddWithValue 传 null 时驱动无法推断参数类型可能把 SQLite 参数类型推断成 TEXT存入后列类型也可能跟着变。显式传 DBNull.Value 并且指定 SqliteType能避免这个隐性问题第 5 章会展开。4.2 查询的两条路DataReader 流式读与 DataTable 一次性读查询几条给 DataGridView 展示还是循环刷几百上千条做处理写法完全不同。数据量大或者要逐行处理的场景用 ExecuteReader它像游标一样一行行读不一次性把结果全放内存using var cmd conn.CreateCommand(); cmd.CommandText SELECT id, device_id, torque_value, record_time FROM device_log ORDER BY id DESC LIMIT 200;; using var reader cmd.ExecuteReader(); while (reader.Read()) { var id reader.GetInt64(0); var devId reader.GetString(1); var torque reader.IsDBNull(2) ? 0 : reader.GetDouble(2); var time reader.GetString(3); Console.WriteLine(${id}\t{devId}\t{torque}\t{time}); }GetInt64、GetString、GetDouble 这些强类型读取方法要求列顺序和 SELECT 的顺序严格对应所以 SELECT 的列尽量按固定顺序写。torque_value 列允许为空读取前必须 IsDBNull 判断直接 GetDouble 遇到 NULL 会抛异常。还有DataReader 必须 Dispose否则它占用的连接不会被归还连接池里的连接会持续保持“忙碌”状态后续操作看起来就像死锁。展示用的小结果集在 System.Data.SQLite 项目里可以走 DataAdapter 一次性填充 DataTableusing var table new DataTable(); using var da new System.Data.SQLite.SQLiteDataAdapter( SELECT id, device_id, torque_value, record_time FROM device_log;, conn); da.Fill(table); dataGridView1.DataSource table;Microsoft.Data.Sqlite 没有 DataAdapterGetDataTable 这种需求就需要自己写循环。把 DataReader 读出的 List 直接绑到 DataGridView 也行只是少了 DataTable 自带的排序视图。桌面工具展示数据默认绑 DataTable 最省事但离线的行编辑会在保存时坑你第 5 章专门讲。4.3 更新与删除精确条件、受影响行数UPDATE 和 DELETE 在 Demo 里看着简单但最容易写出“更新了不该更新的行”的代码。核心纪律UPDATE 必须带 WHERE 条件能用主键就用主键能用业务唯一键也接受但绝不要写无 WHERE 的全表更新。其次用 ExecuteNonQuery 的返回值判断有没有更新到目标行这个返回值是 SQLite 计算的受影响行数。using var cmd conn.CreateCommand(); cmd.CommandText UPDATE device_log SET torque_value $torque, record_time datetime(now,localtime) WHERE id $id;; cmd.Parameters.AddWithValue($id, targetId); cmd.Parameters.AddWithValue($torque, newTorque); var affected cmd.ExecuteNonQuery(); if (affected 0) { // 没有匹配到的 id可能是数据已被删或目标 id 不存在 return false; }DELETE 同理cmd.CommandText DELETE FROM device_log WHERE id $id;; cmd.Parameters.AddWithValue($id, targetId); var deleted cmd.ExecuteNonQuery();affected 0 不一定是错误只是代表条件没匹配到行。如果业务上坚信这个 id 必须存在affected 0 就应该抛异常或写日志而不是静默通过。外键场景也值得提前知道SQLite 默认不启用外键约束PRAGMA foreign_keys ON 需要每条连接单独设置。如果你删父表数据时期望子表级联删除默认行为是什么都不发生。Demo 阶段没有外键就忽略等有了再回来处理。4.4 事务批量操作一次连接加一个事务别每条一提交批量写入的性能问题绝大多数情况下由提交次数决定。SQLite 每提交一个事务都要把数据刷到磁盘并处理 WAL 的 checkpoint这个成本跟缓存和磁盘状态强相关。把 1000 条插入放进一个事务通常比逐条提交快一个数量级。这个数量级来自 I/O 层面的差异不是 C# 代码好坏能弥补的。using var conn OpenAppDb(); using var tx conn.BeginTransaction(); try { for (int i 0; i batchData.Count; i) { using var cmd conn.CreateCommand(); cmd.Transaction tx; cmd.CommandText INSERT INTO device_log (device_id, torque_value) VALUES ($devId, $torque);; cmd.Parameters.AddWithValue($devId, batchData[i].DeviceId); cmd.Parameters.AddWithValue($torque, batchData[i].Torque); cmd.ExecuteNonQuery(); } tx.Commit(); } catch { tx.Rollback(); throw; }这里最容易被忽略的是 cmd.Transaction tx 这一行。如果你创建了事务却没把它赋给命令命令默认在隐式事务里执行效果等价于每条一个事务大量插入照样慢某些驱动甚至直接抛“命令需要关联事务”的异常。提交时机也有讲究不是数据全部处理完必须立刻提交而是要平衡“事务里积攒的锁不阻塞其他连接太久”和“提交次数少”。我一般单个事务控制在一万条以内数量更大就分批事务每批提交一次避免单事务占锁时间太长。5. SQLite3 在 C# 里的 5 个高频坑从锁冲突到路径黑匣子的排查记录5.1 database is locked写锁冲突与 busy_timeout 缺位现象程序跑一会儿写入时报 SqliteException错误信息是 database is locked有时只在压力大的时候出现很玄学。原因SQLite 同一时刻只允许一个写事务。你有一个连接的事务长时间没提交或者某个连接打开事务后忘了 Dispose其他连接的写请求进来后SQLite 默认 busy_timeout 是 0意思是“等都不等立刻告诉你忙”。所以不是数据库坏了是等锁策略太苛刻。解决打开连接后设置 WAL 模式并提高 busy_timeoutusing var conn OpenAppDb(); using var cmd conn.CreateCommand(); cmd.CommandText PRAGMA journal_modeWAL;; cmd.ExecuteScalar(); cmd.CommandText PRAGMA busy_timeout5000;; cmd.ExecuteNonQuery();PRAGMA journal_modeWAL 要用 ExecuteScalar 读结果它会返回一行一列值一般是 wal。WAL 是针对数据库文件的持久设置设置一次之后这个库文件就一直处于 WAL 模式。WAL 的收益是读不阻塞写、写不阻塞读锁冲突大幅下降。再配合 busy_timeout5000写锁冲突时最多等 5 秒再报错而不是立刻失败。Demo 阶段的锁问题基本到此为止。如果 5 秒后还是频繁锁那就要回去查哪个事务没提交了。5.2 BadImageFormatException原生库架构不匹配现象程序一启动就抛 BadImageFormatException或者提示“试图加载格式不正确的程序”。Debug 正常换 Release 崩或者换个机器崩。原因System.Data.SQLite 自带原生 SQLite.Interop.dll加载哪个版本取决于进程位数。项目平台目标是 AnyCPU 时64 位系统上跑 64 位进程程序会去 x64 子目录找原生库但如果某个依赖项强制了平台或者 SQLite.Interop.dll 只复制了 x86 版本进来进程位数和原生库位数对不上就直接崩。解决项目管理器里把平台目标固定成 x64 或 x86并保持一致清理 bin 和 obj 重新编译。检查输出目录dir bin\Release\net48\重点看 SQLite.Interop.dll 是否存在于 x64 和 x86 子目录。如果用 Microsoft.Data.Sqlite这个坑会消失NuGet 按目标 RID 自动选原生库。新项目直接绕开这个历史包袱老项目则把平台目标写死在工程文件里不要留 AnyCPU 的幻想。5.3 类型亲和性存进去 REAL读出来却不对劲现象插入 torque_value 时传的是字符串 58.6查询时发现能查出来但类型有时是 double 有时是 stringWHERE torque_value 60 查不到刚插的数据。原因SQLite 的列有“类型亲和性”但不是强约束。REAL 列允许插入字符串数据实际以 TEXT 形式存进去比较时依赖类型转换规则容易记混。bool 也没有原生类型存进去的 true / false 到底变成 1/0 还是 true取决于你传入值的类型。解决参数显式指定类型不要依赖隐式转换cmd.Parameters.AddWithValue($torque, torqueValue); cmd.Parameters[$torque].SqliteType SqliteType.Real;bool 就转成整数再存。查询时也按预期类型读。规则很简单谁插入谁负责让类型干净别指望 SQLite 帮你转。命令行里看着一切正常C# 里却类型出错先怀疑这个。5.4 相对路径黑匣子数据库到底写进了哪个文件现象Visual Studio 里按 F5 一切正常发布后从资源管理器双击 exe程序运行后报打不开数据库或者正常运行了但数据没存进预期文件。原因连接串里写 Data Sourceappdata.db这种相对路径是相对于“当前工作目录”的。F5 时 VS 把工作目录设成 bin\Debug数据库文件就在那你从别的地方双击 exe工作目录变成别处程序就在别的路径找文件。打不开还算好的更坑的是静默创建一个新库数据写进“另一个文件”看起来数据全没了。解决把数据库路径拼到可执行文件所在目录并提前建目录var dbDir Path.Combine(AppContext.BaseDirectory, data); Directory.CreateDirectory(dbDir); var dbPath Path.Combine(dbDir, app.db); var builder new SqliteConnectionStringBuilder { DataSource dbPath, Mode SqliteOpenMode.ReadWriteCreate }; using var conn new SqliteConnection(builder.ToString());用 AppContext.BaseDirectory 而不是 Environment.CurrentDirectory无论从服务、快捷方式还是计划任务启动路径都不受外部工作目录影响。这是所有 SQLite C# 工程里我认为最值得在 Demo 阶段就写对的一条。等用户报“数据没保存”再回头排查路径成本高得多。5.5 DataGridView 改了不保存离线编辑的边界现象DataGridView.DataSource 绑定 DataTable 后界面里改了单元格、删了行点击保存却发现数据库没变化。原因DataGridView 绑定的是 DataTable 的内存状态改动只发生在内存。System.Data.SQLite 的 SQLiteDataAdapter 可以支持离线更新把内存 DataTable 的差异回写数据库但很多人是在 Microsoft.Data.Sqlite 项目里找 DataAdapter自然找不到更多人其实是操作顺序不对改动后没有任何回写动作。解决Demo 阶段我推荐的策略是“展示绑定写入不绑定”。DataGridView 只用来显示查询结果用户改动、新增、删除时单独收集变更走第 4 章那套参数化命令回写。这样行为完全可控绕开了 DataTable 的 AcceptChanges 时机问题。如果要在 System.Data.SQLite 里做真离线更新注意 Update 前要先把新行的 id 字段处理好主键冲突全藏在那堆 RowState 里。桌面工具的数据提交逻辑永远比 UI 绑定优先级高。6. 把 Demo 拼成一个顺手模块一个带 WAL 检查的 Repository 封装前面五章跑通了增删改查你会注意到连接串和 PRAGMA 到处都是重复代码。我的习惯是最少封装一个静态类把连接创建和初始化统一掉后面的业务代码只关心命令。下面这个封装适合 .NET 6 的控制台或 WinForms 项目public static class AppDb { private static string DbPath { get; } Path.Combine(AppContext.BaseDirectory, data, app.db); public static SqliteConnection Open() { Directory.CreateDirectory(Path.GetDirectoryName(DbPath)!); var conn new SqliteConnection( new SqliteConnectionStringBuilder { DataSource DbPath, Mode SqliteOpenMode.ReadWriteCreate }.ToString()); conn.Open(); using var init conn.CreateCommand(); init.CommandText PRAGMA journal_modeWAL;; init.ExecuteScalar(); return conn; } }调用方拿到连接后在一个 using 块内完成多次读写最后不要手动 Close交给 Dispose 把连接归还连接池。事务和命令仍然按第 4 章的方式在业务层组织这个封装只解决“连接怎么来、路径怎么定、WAL 怎么开”的重复劳动。你要验证它是否有效把 data 目录里的 app.db 用第 3 章的命令行打开查 PRAGMA journal_mode 是否返回 wal再查几张表的行数。如果都对说明从选型到避坑的链路已经闭环。我自己的教训是早期把连接串写在 App.config 里换一台机器部署时路径配置经常漏改后来统一改成代码里基于 AppContext.BaseDirectory 计算再没出过“数据库找不到”的求助。Demo 阶段花十分钟做这个封装比维护一堆零散连接串合算得多。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询