简介这份资源面向使用 C# 进行桌面或移动端数据存储开发的程序员尤其是希望快速上手 SQLite 的初学者与中级开发者。它提供了一套完整的 SQLite 开发包与实例源码帮助解决数据库选型、环境配置和基础操作等问题。压缩包共 85 个文件约 19.17MB包含 52 个 dll 动态库、8 个 cs 源码文件、6 个 exe 可执行程序以及 config、resx、db 等配置与数据文件覆盖 SQLiteStudio 管理工具和 System.Data.SQLite 驱动并附带 SQLiteDBDemo 示例工程。已有 952 人学习下载说明其参考价值得到一定认可。读者可从中获得可直接运行的示例项目、数据库操作代码模板以及轻量级数据库的集成思路便于在 C# 项目中快速实现数据持久化同时理解 SQLite 的独立性、跨平台与多语言接口等特性。1. 从一份 C# SQLite 开发包说起为什么它值得你花一个下午跑通如果你正在用 C# 做上位机、工控采集或者桌面工具大概率绕不开本地数据存储这件事。SQLite 数据库文件轻、零配置、单文件可拷贝配合 C# 的 ADO.NET 用起来非常顺手这也是「C# SQLite 开发包及实例源码」这类资源一直被反复检索的原因。但很多人拿到一个压缩包后卡在第一步里面到底该看哪个文件、引用哪个 DLL、连接字符串怎么写、为什么运行报「找不到 System.Data.SQLite」或者「无法加载 SQLite.Interop.dll」。这篇笔记不假设你手上那份包长什么样而是把 C# 操作 SQLite 最常见的落地路径完整走一遍——从选包、建库、增删改查到参数化、事务、并发和发布踩坑。新手能照着敲出第一个可运行的 CRUD熟手能对照检查自己的封装是不是埋了雷。适合做 C# 上位机、数据采集、离线缓存、单机工具的人。2. 选包与建库C# 连 SQLite 的三条路和最小可跑代码2.1 Microsoft.Data.Sqlite、System.Data.SQLite、SQLitePCLRaw 怎么选C# 连 SQLite 目前主流就三条路选错了后面全是坑。第一条是Microsoft.Data.Sqlite微软官方维护NuGet 直接装跨平台好.NET Core / .NET 5 首选。它底层依赖SQLitePCLRaw所以你会看到包里带了一堆SQLitePCLRaw.*的依赖这是正常的不要手动去删。第二条是System.Data.SQLite老牌库历史项目多WinForm / WPF 老工程里常见。它的特点是带SQLite.Interop.dll这个原生库x86 和 x64 是分开的这是后面「无法加载 DLL」问题的根源。第三条是直接用SQLitePCLRaw裸调一般人不建议除非你要极致控制。我一般的判断标准很简单新项目一律Microsoft.Data.Sqlite维护老项目、且已经用了System.Data.SQLite的不要为了「新」去换迁移成本不值。下面统一用Microsoft.Data.Sqlite演示因为它是当前最省心的选择。安装命令# 新项目首选官方维护跨平台 dotnet add package Microsoft.Data.Sqlite # 老项目如果已经在用保持不动即可 # dotnet add package System.Data.SQLite参数说明Microsoft.Data.Sqlite不需要额外装原生包NuGet 会自动带上对应平台的SQLitePCLRaw。如果你在 .NET Framework 项目里用它注意目标框架至少要 4.6.1 以上否则会有兼容问题。2.2 用连接字符串建库并跑通第一张表SQLite 最大的特点是「库即文件」连接字符串指向一个不存在的文件时默认会自动创建。这一点和 SQL Server 完全不同很多人第一次用会懵。using Microsoft.Data.Sqlite; // Data Source 指向 .db 文件文件不存在会自动创建 // CacheShared 让同一进程内多个连接共享缓存减少锁冲突 var connStr Data Sourceappdata.db;CacheShared; using var conn new SqliteConnection(connStr); conn.Open(); using var cmd conn.CreateCommand(); cmd.CommandText CREATE TABLE IF NOT EXISTS DeviceLog ( Id INTEGER PRIMARY KEY AUTOINCREMENT, DeviceNo TEXT NOT NULL, Value REAL NOT NULL, Created TEXT NOT NULL );; cmd.ExecuteNonQuery();逻辑说明CREATE TABLE IF NOT EXISTS保证重复运行不报错适合程序启动时初始化。INTEGER PRIMARY KEY AUTOINCREMENT是 SQLite 的自增主键写法注意它和 MySQL 的AUTO_INCREMENT拼写不同。时间字段我用TEXT存 ISO8601 字符串而不是用 SQLite 的日期类型——SQLite 本身没有真正的日期类型存字符串最省心排序和比较都正常。参数说明Data Source是唯一必填项相对路径相对于程序工作目录建议用绝对路径避免发布后找不到文件。CacheShared在多线程场景下能减少「database is locked」但它不是万能药后面避坑章节会细说。2.3 参数化插入别用字符串拼接这是血泪经验最多的地方。字符串拼接 SQL 不只是注入问题还会因为单引号、日期格式、浮点精度各种翻车。using var conn new SqliteConnection(Data Sourceappdata.db); conn.Open(); using var tran conn.BeginTransaction(); // 批量插入必须开事务否则慢到怀疑人生 using var cmd conn.CreateCommand(); cmd.CommandText INSERT INTO DeviceLog (DeviceNo, Value, Created) VALUES ($no, $val, $time); // 参数只创建一次循环里改值即可这是性能关键 var pNo cmd.Parameters.Add($no, SqliteType.Text); var pVal cmd.Parameters.Add($val, SqliteType.Real); var pTime cmd.Parameters.Add($time, SqliteType.Text); for (int i 0; i 1000; i) { pNo.Value DEV- i; pVal.Value i * 1.5; pTime.Value DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss); cmd.ExecuteNonQuery(); } tran.Commit();逻辑说明SQLite 的参数前缀支持$、、:三种$是官方推荐写法。参数对象在循环外创建、循环内只改Value避免每次重新解析 SQL。1000 条插入如果不开事务SQLite 会为每条语句做一次磁盘同步实测能慢几十倍这是新手最容易忽略的性能坑。参数说明SqliteType.Real对应 C# 的doubleSqliteType.Text对应string。如果你传DateTime对象而不转字符串Microsoft.Data.Sqlite会按自己的规则序列化跨库读取时格式可能对不上所以我习惯统一转字符串。3. 查询、更新与事务把 CRUD 写对写稳3.1 读取用 ExecuteReader注意列序号和 NULL查询看起来简单但NULL处理和列读取方式经常出问题。using var conn new SqliteConnection(Data Sourceappdata.db); conn.Open(); using var cmd conn.CreateCommand(); cmd.CommandText SELECT Id, DeviceNo, Value, Created FROM DeviceLog WHERE DeviceNo $no; cmd.Parameters.AddWithValue($no, DEV-1); using var reader cmd.ExecuteReader(); while (reader.Read()) { // 用列名读取比序号可读但序号略快列多时建议用序号 long id reader.GetInt64(0); string no reader.GetString(1); double val reader.GetDouble(2); // Created 可能为 NULL必须先判断否则 GetString 抛异常 string time reader.IsDBNull(3) ? : reader.GetString(3); Console.WriteLine(${id} {no} {val} {time}); }逻辑说明reader.Read()返回 false 表示没有更多行。IsDBNull判断是必须的SQLite 允许任意列存 NULL直接GetString会抛InvalidCastException。用列名读取reader.GetOrdinal(DeviceNo)可读性好但每次调用有查找开销列多、循环大时用序号更快。参数说明AddWithValue方便但会做类型推断某些情况下推断不准比如把int推成Int64导致索引失效生产代码建议显式指定SqliteType。3.2 更新和删除WHERE 条件写错就是全表事故using var conn new SqliteConnection(Data Sourceappdata.db); conn.Open(); using var cmd conn.CreateCommand(); // 更新务必带 WHERE否则整表被改 cmd.CommandText UPDATE DeviceLog SET Value $val WHERE Id $id; cmd.Parameters.AddWithValue($val, 99.9); cmd.Parameters.AddWithValue($id, 1); int affected cmd.ExecuteNonQuery(); Console.WriteLine($更新了 {affected} 行); // 删除同理 cmd.CommandText DELETE FROM DeviceLog WHERE Created $before; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue($before, 2024-01-01 00:00:00); Console.WriteLine($删除了 {cmd.ExecuteNonQuery()} 行);逻辑说明ExecuteNonQuery返回受影响行数这是判断操作是否命中的唯一可靠依据。我见过太多人 UPDATE 忘了 WHERE把整张表刷成同一个值然后来找我恢复数据——SQLite 没有回滚日志给你后悔药只能靠备份。参数说明Parameters.Clear()在复用同一个cmd对象时必须调用否则旧参数会残留导致「参数数量不匹配」错误。这也是复用 command 的常见坑。3.3 事务的三种写法和隔离级别SQLite 的事务和别的数据库不太一样它靠文件锁实现理解这点能省很多调试时间。using var conn new SqliteConnection(Data Sourceappdata.db); conn.Open(); // 写法一BeginTransaction 默认Deferred第一次写才加锁 using (var tran conn.BeginTransaction()) { // ... 多条语句 tran.Commit(); } // 写法二Immediate立即拿写锁适合确定要写的场景减少锁升级失败 using (var tran conn.BeginTransaction(System.Data.IsolationLevel.Serializable)) { // Microsoft.Data.Sqlite 里 Serializable 对应 Immediate 行为 tran.Commit(); }逻辑说明SQLite 的Deferred事务在第一条读语句时拿共享锁第一条写语句时尝试升级为排他锁如果此时别的连接也在读升级会失败并抛SQLITE_BUSY。Immediate一开始就拿写锁避免升级失败代价是并发读会被挡。工控采集这种「写多读少」的场景我一般直接用 Immediate。参数说明IsolationLevel.Serializable在Microsoft.Data.Sqlite中映射为 Immediate。如果你用System.Data.SQLite可以直接写conn.BeginTransaction(true)参数true表示 immediate。4. 避坑与排查C# SQLite 最常见的 5 个翻车现场4.1 无法加载 SQLite.Interop.dll现象程序在开发机跑得好好的拷到客户机器或发布后报Unable to load DLL SQLite.Interop.dll。原因这是System.Data.SQLite特有的问题。它把原生库放在x86和x64两个子目录里运行时按进程位数去找。如果你的项目平台目标设成Any CPU且没勾「首选 32 位」在某些宿主环境比如被 32 位程序加载下会找错目录。解决把项目平台目标明确设成x64或x86不要用Any CPU发布时确认x64\SQLite.Interop.dll被一起拷出去。如果换用Microsoft.Data.Sqlite这个问题基本不存在因为它的原生库由SQLitePCLRaw按 RID 自动处理。4.2 database is locked现象多线程或多进程访问时报SQLite Error 5: database is locked。原因SQLite 同一时刻只允许一个写操作。默认 journal 模式下写的时候会锁整个库。多个连接同时写或者一个长事务没提交另一个连接就会撞锁。解决第一写操作尽量短别在事务里做耗时计算或网络请求第二开启 WAL 模式让读写不互相阻塞using var cmd conn.CreateCommand(); cmd.CommandText PRAGMA journal_modeWAL;; cmd.ExecuteNonQuery();WAL 模式下读不挡写、写不挡读只有写和写之间互斥。第三设置 busy timeout让连接在锁冲突时等待而不是立刻报错连接字符串加Default Timeout30或执行PRAGMA busy_timeout30000;。4.3 中文乱码或日期格式对不上现象存进去的中文读出来是问号或者日期比较结果不对。原因SQLite 默认编码是 UTF-8Microsoft.Data.Sqlite全程用 UTF-8正常不会有乱码。乱码通常来自用外部工具比如某些老版本 DB Browser以 GBK 打开或者用System.Data.SQLite时连接字符串没指定编码。日期问题则是存了非标准格式的字符串导致字符串比较失效。解决统一用yyyy-MM-dd HH:mm:ss这种可排序格式存时间中文确保连接和工具都用 UTF-8。用 DB Browser for SQLite 查看时确认它的编码设置是 UTF-8。4.4 自增主键跳号或复用现象删了几行后新插入的 Id 不是连续的或者删掉最大 Id 后新数据复用了这个 Id。原因SQLite 的AUTOINCREMENT和普通INTEGER PRIMARY KEY行为不同。普通INTEGER PRIMARY KEY会复用已删除的最大 Id只有显式写AUTOINCREMENT才会严格递增且永不复用代价是多维护一张sqlite_sequence表。解决如果业务要求 Id 绝不复用比如做对外单号必须写AUTOINCREMENT如果只是内部主键普通写法性能更好跳号无所谓。4.5 发布后数据库文件被写进 Program Files现象程序装到C:\Program Files\下后插入数据报「attempt to write a readonly database」。原因Program Files目录默认需要管理员权限才能写。你的连接字符串用了相对路径数据库文件被创建在程序目录下普通用户权限写不进去。解决数据库文件放到Environment.GetFolderPath(SpecialFolder.ApplicationData)或LocalApplicationData下用绝对路径连接。这是桌面程序的基本规范别图省事放程序目录。5. 进阶技巧加密、批量与验证你的封装5.1 给 SQLite 数据库文件加密SQLite 数据库文件默认是明文的用记事本都能看到部分内容。如果数据敏感常见做法是用 SQLCipher。Microsoft.Data.Sqlite本身不带加密需要换成Microsoft.Data.Sqlite.Core加SQLitePCLRaw.bundle_e_sqlcipher然后在连接字符串里加Password// 需要引用 SQLitePCLRaw.bundle_e_sqlcipher var connStr Data Sourcesecure.db;Passwordyour_key; using var conn new SqliteConnection(connStr); conn.Open(); // 首次创建后文件即为加密状态用普通工具打开是乱码参数说明Password是 SQLCipher 的密钥一旦设置后续所有连接都必须带同样的密钥否则报「file is not a database」。密钥丢了数据就真没了没有后悔药务必做好密钥管理。注意加密后性能会有一定下降写入密集场景要实测。5.2 批量写入用事务加预编译实测差几十倍前面提过事务这里给一组可对比的验证方法让你自己确认封装有没有问题。写入方式10000 条耗时参考说明逐条 ExecuteNonQuery无事务数十秒每条一次磁盘同步单事务 复用参数数百毫秒推荐做法单事务 每次新建 command1 秒左右有解析开销多线程并发写更慢且易锁SQLite 不适合并发写验证方法写个小基准分别跑上面几种用Stopwatch计时。如果你的封装在 10000 条上花了十几秒基本可以确定没开事务或没复用参数。5.3 用 DB Browser for SQLite 验证数据别只信代码代码写对了不代表数据对。我习惯每做完一个模块用 DB Browser for SQLite 打开 .db 文件肉眼确认表结构、索引、数据行数和时间格式。这个工具能直接看 BLOB、执行 SQL、导出 CSV排查「代码说插入了但查不到」这类玄学问题特别有效。注意打开前先关掉程序里的连接否则可能因为锁看不到最新数据或者工具提示只读。5.4 封装一个可复用的 SqliteHelper 要盯住哪几点最后落到封装。很多人拿到实例源码就直接抄一个 Helper 类但抄完不知道哪里该改。我的检查清单是连接是否用using及时释放写操作是否默认包事务参数是否显式指定类型异常是否区分「锁冲突」和「真错误」数据库路径是否可配置而不是写死。这五点做到了这个 Helper 才敢放到生产环境。至于实例源码里的具体类名和方法签名各家写法不同理解上面这些判断标准比死记代码更重要。我自己这些年最大的习惯就是任何涉及写库的改动先在测试库上跑一遍再用 DB Browser 确认数据最后才上真实环境。SQLite 没有服务端帮你兜底文件坏了就是坏了多花两分钟验证比事后恢复省心得多。希望帮到你。本文还有配套的精品资源点击获取