简介:这份资源面向使用 C# 进行桌面或移动端数据存储开发的程序员,尤其是希望快速上手 SQLite 的初学者与中级开发者。它提供了一套完整的 SQLite 开发包与实例源码,帮助解决数据库选型、环境配置和代码集成中的常见问题。压缩包共 85 个文件,约 19.17MB,包含 52 个 dll 动态库、8 个 cs 源码文件、6 个 exe 可执行程序,以及 config、resx、sln、csproj 等工程配置与资源文件,另附一个 db 数据库文件,覆盖从依赖库到可运行示例的完整链路。其中 System.Data.SQLite 与 SQLiteDBDemo 示例工程可直接参考,便于理解连接、查询与事务处理。SQLite 本身轻量、独立、跨平台且支持多语言接口,通过独占与共享锁保障事务安全。目前已有 952 人学习下载,适合需要快速搭建本地数据存储方案的开发者借鉴。
1. 拿到 C# SQLite 开发包先别急着引用:这份资源到底能省掉哪些重复劳动
如果你写过 C# 上位机、做过本地数据缓存、或者维护过单机版 WinForm/WPF 工具,大概率都绕不开 SQLite。它不需要独立服务进程,一个.db文件就能跑,部署时少一层安装配置,对现场交付特别友好。但真到动手阶段,很多人卡在同一个地方:System.Data.SQLite、Microsoft.Data.Sqlite、EF Core 的 SQLite Provider 到底选哪个,原生 DLL 怎么随程序走,32 位和 64 位怎么同时兼容,连接字符串写错一个参数就报“无法加载 DLL”或者“database is locked”。
这份《C# SQLite 开发包及实例源码.zip》解决的正是这类落地问题。它不是单纯丢一个 NuGet 包名给你,而是把开发包和可运行的实例源码放在一起,让你能看到建库、建表、增删改查、事务、参数化查询这些动作在 C# 里到底怎么落。适合两类人:一类是刚接触 C# 数据库编程、需要一份能跑通的参照;另一类是老手,想拿一套现成的目录结构和封装思路,省掉每次新项目重新搭一遍的功夫。下面按“资源是什么、怎么接进项目、坑在哪、怎么验证”一路拆下去。
2. 开发包与实例源码的结构拆解:先认清你手里有哪些文件
2.1 开发包通常包含的三类东西
拿到压缩包,先别双击.sln就编译。我一般会先按类型过一遍目录,确认里面到底有什么。C# SQLite 开发包这类资源,常见构成是三类:第一类是托管程序集,也就是System.Data.SQLite.dll这种可以直接在 C# 里using的库;第二类是原生互操作 DLL,比如SQLite.Interop.dll,它才是真正干活的底层;第三类是实例源码,通常是一个或多个.cs文件、控制台项目或 WinForm 项目,演示连接、查询、事务。
这里有个关键认知:System.Data.SQLite是“托管层 + 原生层”混合的。托管 DLL 负责把 ADO.NET 的接口翻译成 SQLite 能懂的调用,原生 DLL 负责实际读写文件。很多人只引用了托管 DLL,运行时报Unable to load DLL 'SQLite.Interop.dll',就是因为原生那半没到位。实例源码的价值在于,它往往已经把这两层的引用关系和输出目录配置好了,你照着抄结构比看文档快。
2.2 用表格快速判断实例源码覆盖了哪些场景
不同开发包附带的实例深度差别很大。有的只给一个ExecuteNonQuery示例,有的会把事务、参数化、DataReader、DataSet 都覆盖。打开源码前,可以先扫一眼有没有下面这些关键词,判断它值不值得细看。
| 场景 | 典型类/方法 | 是否体现资源价值 |
|---|---|---|
| 建立连接 | SQLiteConnection、Open() | 看连接字符串写法 |
| 建表 | CREATE TABLE+ExecuteNonQuery | 看字段类型映射 |
| 插入/更新 | 参数化SQLiteCommand | 看是否防注入 |
| 查询 | ExecuteReader、SQLiteDataAdapter | 看读取方式 |
| 事务 | SQLiteTransaction、BeginTransaction | 看批量写入性能 |
| 异常处理 | try/catch+SQLiteException | 看错误码处理 |
如果实例里连事务和参数化都没有,那它只能算入门演示,你得自己补。反过来,如果它把SQLiteDataAdapter和DataSet都写了,说明作者考虑过离线数据场景,参考价值更高。
2.3 实例源码的目录组织透露的工程习惯
我见过不少实例把所有代码塞进一个Program.cs,也见过按Models、DAL、Utils分层的。前者适合快速验证,后者适合直接搬进项目。判断标准很简单:看有没有独立的数据库帮助类。常见做法是写一个SQLiteHelper,把连接字符串、打开连接、执行命令、返回 DataTable 封装成静态方法。这种类一旦有了,你后面换库、加日志、改连接方式都只动一个地方。
提示:如果实例里的连接字符串是硬编码的绝对路径,先改成相对路径或可配置项再跑,否则换台机器就找不到库文件。
3. 把开发包接进 C# 项目:引用、连接字符串与第一个可跑通的 CRUD
3.1 引用方式:NuGet 还是手动引 DLL
这一步是分叉口。如果你拿到的开发包里有.nupkg或者作者说明用 NuGet,优先走 NuGet,因为依赖和原生 DLL 的复制规则它帮你处理了。常见做法是在 Visual Studio 里右键项目 → 管理 NuGet 程序包 → 搜索System.Data.SQLite或Microsoft.Data.Sqlite。前者历史久、资料多,后者是微软现在主推的轻量版,API 更现代。
如果开发包是手动引 DLL 的形式,那就得自己保证SQLite.Interop.dll被复制到输出目录。做法是在项目里建x86和x64两个子目录,各放一份对应架构的 Interop DLL,然后在.csproj里配置复制。下面是一个典型的引用配置片段:
<ItemGroup> <!-- 托管程序集引用 --> <Reference Include="System.Data.SQLite"> <HintPath>lib\System.Data.SQLite.dll</HintPath> </Reference> </ItemGroup> <ItemGroup> <!-- 确保原生 DLL 按架构复制到输出目录 --> <None Include="lib\x86\SQLite.Interop.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <Link>x86\SQLite.Interop.dll</Link> </None> <None Include="lib\x64\SQLite.Interop.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <Link>x64\SQLite.Interop.dll</Link> </None> </ItemGroup>逻辑说明:Reference负责编译期能找到托管类型;两个None节点负责把原生 DLL 按x86、x64子目录结构复制到bin下。System.Data.SQLite运行时会根据当前进程位数去对应子目录找 Interop,所以目录名不能写错。参数上,CopyToOutputDirectory用PreserveNewest表示源文件更新时才覆盖,避免每次全量复制拖慢编译。
3.2 连接字符串的三种写法与适用场景
连接字符串是 SQLite 最容易写错的地方。常见有三种:
// 写法一:基础文件路径,适合单机固定目录 string connStr1 = "Data Source=app.db;Version=3;"; // 写法二:带密码,适合对文件加密有要求的场景 string connStr2 = "Data Source=app.db;Version=3;Password=myPass;"; // 写法三:内存库,适合单元测试或临时计算 string connStr3 = "Data Source=:memory:;Version=3;";Data Source是库文件路径,相对路径以程序工作目录为基准,这点在服务里跑容易翻车。Version=3是 SQLite 3 的固定写法,别省。Password只有在使用了加密版本的 SQLite 时才生效,普通版本加了也白加。内存库每次连接都是全新的,连接一关数据就没了,适合测试不适合持久化。
3.3 一个最小可跑的 CRUD 实例
下面这段代码把建表、插入、查询串起来,可以直接放进控制台项目跑。它对应实例源码里最常见的流程。
using System; using System.Data.SQLite; class Program { static void Main() { // 连接字符串指向当前目录下的 test.db string connStr = "Data Source=test.db;Version=3;"; using (var conn = new SQLiteConnection(connStr)) { conn.Open(); // 建表:IF NOT EXISTS 避免重复执行报错 string createSql = @"CREATE TABLE IF NOT EXISTS User ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL, Age INTEGER)"; using (var cmd = new SQLiteCommand(createSql, conn)) { cmd.ExecuteNonQuery(); } // 参数化插入:防止 SQL 注入,也避免拼接引号出错 string insertSql = "INSERT INTO User (Name, Age) VALUES (@name, @age)"; using (var cmd = new SQLiteCommand(insertSql, conn)) { cmd.Parameters.AddWithValue("@name", "张三"); cmd.Parameters.AddWithValue("@age", 28); int rows = cmd.ExecuteNonQuery(); Console.WriteLine($"插入 {rows} 行"); } // 查询:用 ExecuteReader 逐行读 string querySql = "SELECT Id, Name, Age FROM User"; using (var cmd = new SQLiteCommand(querySql, conn)) using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { Console.WriteLine($"{reader.GetInt32(0)} {reader.GetString(1)} {reader.GetInt32(2)}"); } } } } }逻辑说明:using保证连接和命令对象及时释放,SQLite 文件锁对未释放连接很敏感。AddWithValue把参数和值绑定,SQL 里用@name占位,这是防注入的标准做法。ExecuteReader适合只读遍历,如果要把结果丢给界面绑定,可以换成SQLiteDataAdapter填充DataTable。参数上,AUTOINCREMENT只在INTEGER PRIMARY KEY上生效,写成其他类型不会自增。
4. 避坑与排查:SQLite 在 C# 里最常见的五类翻车
4.1 现象:运行时报“无法加载 DLL SQLite.Interop.dll”
原因基本是原生 DLL 没复制到输出目录,或者位数不匹配。32 位进程去x64目录找,或者根本没建x86/x64子目录,都会触发。解决方式是检查bin\Debug下有没有对应架构的 Interop,没有就回到 3.1 的.csproj配置,确认Link路径写的是x86\SQLite.Interop.dll这种带子目录的形式。另外,项目平台目标如果是Any CPU,在 64 位系统上默认跑 64 位,但某些宿主会强制 32 位,最好显式指定x86或x64。
4.2 现象:写入时抛“database is locked”
这是 SQLite 的并发模型决定的,它同一时刻只允许一个写事务。常见触发场景是:一个连接没关就开另一个写连接,或者DataReader没读完就执行更新。解决方式是缩短连接持有时间,写完立刻释放;批量写入用事务包起来,减少锁竞争;读的时候如果只是遍历,尽快Closereader。如果确实有多线程写入需求,常见做法是加一个写队列,让写操作串行化,而不是硬扛并发。
4.3 现象:中文写入后查询显示乱码
SQLite 本身用 UTF-8 存储,C# 字符串是 UTF-16,正常情况 ADO.NET 会帮你转。乱码往往出在连接字符串没指定编码,或者用外部工具打开时编码选错。解决方式是确认连接字符串没有奇怪的Encoding参数,查询时用reader.GetString而不是GetValue再强转。如果要把.db文件发给别人用 DB Browser for SQLite 看,让对方用 UTF-8 打开即可。
4.4 现象:自增主键插入后拿不到新 Id
ExecuteNonQuery只返回受影响行数,不返回新 Id。要拿自增 Id,得用last_insert_rowid()。常见做法是在同一个连接、同一个事务里,插入后立刻执行SELECT last_insert_rowid()。注意必须用同一个连接,换连接拿到的可能是别的值。实例源码里如果没写这一步,你自己补上。
using (var cmd = new SQLiteCommand("SELECT last_insert_rowid()", conn)) { long newId = (long)cmd.ExecuteScalar(); Console.WriteLine($"新记录 Id={newId}"); }4.5 现象:部署到客户机器后程序启动就崩
开发机跑得好好的,换台机器就挂,多半是缺少 Visual C++ 运行库,或者原生 DLL 被杀毒软件拦截。System.Data.SQLite的原生部分依赖 VC++ 运行时,目标机器没装对应版本就会加载失败。解决方式是在安装包里带上 VC++ Redistributable,或者改用纯托管的Microsoft.Data.Sqlite(它底层用 SQLitePCLRaw,依赖处理方式不同)。另外,现场机器如果有安全软件,把程序目录加白名单,避免 Interop DLL 被误删。
5. 进阶用法与验证:事务批量写入、EF Core 接入与一套自检习惯
5.1 用事务把批量插入从秒级压到毫秒级
单条插入每次都会触发一次磁盘同步,一千条就是一千次,慢得让人怀疑人生。把批量插入包进一个事务,SQLite 只在提交时同步一次,速度差几十倍是常态。下面这段是实例源码里值得单独拎出来的写法:
using (var conn = new SQLiteConnection("Data Source=test.db;Version=3;")) { conn.Open(); using (var trans = conn.BeginTransaction()) using (var cmd = new SQLiteCommand("INSERT INTO User (Name, Age) VALUES (@n, @a)", conn, trans)) { // 预定义参数,循环里只改值,避免重复创建命令对象 cmd.Parameters.Add(new SQLiteParameter("@n", System.Data.DbType.String)); cmd.Parameters.Add(new SQLiteParameter("@a", System.Data.DbType.Int32)); for (int i = 0; i < 1000; i++) { cmd.Parameters["@n"].Value = "用户" + i; cmd.Parameters["@a"].Value = 20 + (i % 30); cmd.ExecuteNonQuery(); } trans.Commit(); } }逻辑说明:BeginTransaction开启事务,命令对象绑定trans后所有操作都在同一事务里。参数对象在循环外创建,循环内只改Value,减少对象分配。Commit之前如果抛异常,事务会自动回滚,数据不会写一半。参数上,SQLiteParameter显式指定DbType比AddWithValue更可控,尤其是日期和二进制字段。
5.2 想上 EF Core 时怎么和这份开发包衔接
如果你的项目规模上来了,手写 SQL 维护成本高,可以换 EF Core 的 SQLite Provider。它和System.Data.SQLite不是一套东西,但概念相通。常见做法是建一个DbContext,用UseSqlite指定连接字符串,实体类映射表。迁移命令用dotnet ef migrations add和dotnet ef database update。注意 EF Core 默认用Microsoft.Data.Sqlite,如果你手里开发包是System.Data.SQLite,两者不要混在同一个项目里引,容易冲突。
5.3 一套我每次交付前都会走的自检清单
被 SQLite 坑过几次之后,我养成了一个习惯:程序打包前,在一台干净机器上按下面顺序过一遍。第一,确认bin目录下x86和x64两个子目录都存在且 Interop DLL 齐全;第二,把程序拷到没有开发环境的机器上双击运行,看能否建库;第三,用 DB Browser for SQLite 打开生成的.db,确认表结构和中文数据正常;第四,模拟断电或强杀进程,再启动看数据库有没有损坏,必要时开启 WAL 模式提升抗崩溃能力。WAL 模式可以在连接后执行PRAGMA journal_mode=WAL;开启,它把写操作先记日志再合并,读写并发更好,代价是多出-wal和-shm两个临时文件。
从那以后我每次把带 SQLite 的程序交给现场,都会强制走一遍这套自检,尤其是位数和运行库这两项,翻车概率最高。希望帮到你。
本文还有配套的精品资源,点击获取