简介这份C# SQLite开发包及实例源码面向需要在本机或移动端嵌入轻量数据库的.NET开发者尤其适合刚接触SQLite、希望快速跑通增删改查与事务处理的初中级程序员。资源包共85个文件以52个dll动态库、8个cs源码、6个exe可执行程序为主另含config配置、resx资源、db数据库与sln解决方案等压缩后约19.17MB既有可直接运行的SQLiteStudio管理工具也包含System.Data.SQLite驱动与SQLiteDBDemo示例工程方便对照学习连接字符串、命令执行与数据绑定写法。SQLite本身无需安装、单文件存储、支持多语言接口与跨平台运行并通过独占与共享锁实现独立事务这些特性在示例中均有体现。目前已有952人学习下载适合作为桌面小工具、本地缓存或Android嵌入场景的入门参考帮助读者理解驱动引用、工程结构与基本操作流程。1. 拿到一个 C# SQLite 开发包先别急着双击 .sln你从同事手里接过一个C# SQLite开发包及实例源码.zip解压后看到一堆.cs、.csproj、.db文件第一反应可能是找.sln双击打开、F5 跑起来。但十有八九会翻车要么提示System.Data.SQLite版本不匹配要么SQLite.Interop.dll找不到要么数据库文件被锁住打不开。这个标题背后真正要解决的问题不是「怎么新建一个 SQLite 连接」而是「怎么把一份别人写好的 C# SQLite 开发包和实例源码在自己的机器上完整跑通并且能改、能扩展、能用到上位机项目里」。它适合三类人正在做 C# 上位机、需要本地轻量存储的工程师想找一个能直接抄的 SQLite 增删改查封装模板的初学者以及手里已经有一份开发包源码、但被 x86/x64 和 Interop 问题卡住的人。下面我按「先认清包里有什么 → 再跑通最小实例 → 再做封装和参数调优 → 最后避坑」的顺序讲每一步都落到可复现的命令和代码上。2. 拆开开发包先分清 SQLite 在 C# 里的三种接入方式2.1 开发包里常见的三类依赖先对号入座拿到C# SQLite开发包及实例源码.zip不要急着编译。先看.csproj或packages.config里引用了什么。C# 操作 SQLite 常见有三条路线开发包用哪条决定了你后面怎么配。接入方式典型引用特点适合场景System.Data.SQLiteSystem.Data.SQLite.dllSQLite.Interop.dll官方老牌功能全需处理 x86/x64传统 WinForm/WPF 上位机Microsoft.Data.SqliteNuGet 包Microsoft.Data.Sqlite微软维护跨平台依赖少.NET Core/.NET 6 新项目SQLitePCLRaw EF CoreMicrosoft.EntityFrameworkCore.SqliteORM 方式写实体类即可需要 LINQ、迁移的项目如果你打开实例源码看到using System.Data.SQLite;那基本就是第一条路线。这条路线最大的特点也是最大的坑它依赖一个原生的SQLite.Interop.dll而这个 dll 分 x86 和 x64 两个版本。很多开发包解压后bin目录里只有一份或者两份混在一起导致运行时直接抛Unable to load DLL SQLite.Interop.dll。先做一件事在解压目录里搜索SQLite.Interop.dll看清楚它出现在哪些子目录下。常见结构是x86/SQLite.Interop.dll和x64/SQLite.Interop.dll各一份由System.Data.SQLite.dll在运行时按进程位数自动加载。如果你的项目是 AnyCPU 且没做处理在 64 位系统上跑 32 位进程就会找不到对应文件。2.2 用命令行确认开发包能不能编译别依赖 IDE在动手改代码前先用dotnet或msbuild在命令行编译一次把环境问题和代码问题分开。假设开发包根目录有一个SQLiteDemo.csproj# 进入开发包目录先还原依赖 dotnet restore SQLiteDemo.csproj # 指定平台编译避免 AnyCPU 带来的 Interop 加载歧义 dotnet build SQLiteDemo.csproj -c Debug -p:Platformx64这两条命令的含义restore负责把 NuGet 包拉下来如果开发包用的是packages.config老格式这一步可能不生效需要改用nuget restore。build时显式指定Platformx64是为了让输出目录里带上正确的SQLite.Interop.dll。如果你不确定开发包是哪种包管理格式看根目录有没有packages.config有就是老格式没有且.csproj里有PackageReference就是新格式。编译成功后去bin/Debug下确认三样东西System.Data.SQLite.dll、SQLite.Interop.dll、以及实例用的.db文件。少任何一样运行都会出问题。这一步看起来笨但能帮你把「环境问题」和「代码问题」彻底分开后面调试会省很多时间。2.3 实例源码里最值得先读的两个文件一个典型的 C# SQLite 实例源码包文件很多但真正决定你能不能跑通的往往只有两个数据库连接封装类和主入口。连接封装类通常叫SQLiteHelper.cs或DbHelper.cs里面会有连接字符串、打开/关闭连接、执行 SQL 的方法。主入口就是Program.cs或某个窗体的Load事件。先读连接字符串。常见写法是string connStr Data Sourcetest.db;Version3;;这里Data Source是数据库文件路径可以是相对路径也可以是绝对路径。相对路径是相对于程序的工作目录不是相对于 exe 所在目录这一点很多人搞混。Version3表示 SQLite 3 格式。如果实例里还写了Passwordxxx那说明这个包用了加密版 SQLite普通System.Data.SQLite打不开需要对应的加密版本这是后面避坑章节要讲的一个大坑。读主入口时重点看它有没有在启动时自动建表、插入测试数据。如果有先别改逻辑直接跑看能不能在bin目录下生成.db文件。能生成说明开发包基本可用不能生成再回头查 Interop 和路径问题。3. 跑通第一个实例从建库到增删改查的最小闭环3.1 用一段最小代码验证 SQLite 连接是否真的可用不要一上来就跑完整实例先写一个最小验证程序。新建一个控制台项目引用开发包里的System.Data.SQLite.dll然后写下面这段using System; using System.Data.SQLite; class Program { static void Main() { // 数据库文件放在当前工作目录避免路径歧义 string dbPath minimal_test.db; string connStr $Data Source{dbPath};Version3;; // 如果文件不存在CreateFile 会新建一个空库 if (!System.IO.File.Exists(dbPath)) { SQLiteConnection.CreateFile(dbPath); } using (var conn new SQLiteConnection(connStr)) { conn.Open(); // 建一张最小表只放两个字段 string createSql CREATE TABLE IF NOT EXISTS Demo (Id INTEGER PRIMARY KEY, Name TEXT); using (var cmd new SQLiteCommand(createSql, conn)) { cmd.ExecuteNonQuery(); } // 插入一条数据用参数化避免拼接 string insertSql INSERT INTO Demo (Name) VALUES (name); using (var cmd new SQLiteCommand(insertSql, conn)) { cmd.Parameters.AddWithValue(name, first_row); cmd.ExecuteNonQuery(); } // 查询并打印 string selectSql SELECT Id, Name FROM Demo; using (var cmd new SQLiteCommand(selectSql, conn)) using (var reader cmd.ExecuteReader()) { while (reader.Read()) { Console.WriteLine($Id{reader.GetInt32(0)}, Name{reader.GetString(1)}); } } } Console.WriteLine(done); } }这段代码的逻辑说明SQLiteConnection.CreateFile只在文件不存在时创建空数据库文件不会覆盖已有数据。using保证连接和命令对象及时释放避免文件被锁。插入时用name参数化而不是字符串拼接这是防止 SQL 注入和特殊字符出错的基本习惯。查询用ExecuteReader逐行读GetInt32(0)和GetString(1)按列序号取值比列名取值稍快但列名更直观实例源码里两种都有。参数说明Data Source如果写相对路径程序的工作目录决定文件位置。在 Visual Studio 里调试时工作目录默认是bin/Debug在命令行运行时是当前终端目录。如果你发现数据库文件「找不到」先打印Environment.CurrentDirectory确认工作目录。3.2 把实例源码里的增删改查封装看懂并改造成自己的跑通最小验证后再回头看开发包里的SQLiteHelper。一个常见的封装长这样public static int ExecuteNonQuery(string sql, params SQLiteParameter[] parameters) { using (var conn new SQLiteConnection(connStr)) { conn.Open(); using (var cmd new SQLiteCommand(sql, conn)) { if (parameters ! null parameters.Length 0) { cmd.Parameters.AddRange(parameters); } return cmd.ExecuteNonQuery(); } } }这个封装的优点是简单直接缺点是每次调用都新开一个连接。SQLite 的连接开销很小这种写法在单线程上位机里够用。但如果你在循环里调用几千次就会明显变慢。改进方式是传入一个已打开的SQLiteConnection或者用事务包起来。改造时我一般会加一个ExecuteScalar和一个泛型查询方法方便取单个值和映射到对象。但不要一上来就上 EF Core 或 Dapper先把原生 ADO.NET 的封装吃透后面换 ORM 只是换写法底层还是这些。3.3 用事务把批量插入从「秒级」压到「毫秒级」实例源码里如果有批量插入大概率是逐条ExecuteNonQuery。SQLite 默认每条语句自动提交每次提交都要写磁盘几千条下来会非常慢。正确做法是用事务using (var conn new SQLiteConnection(connStr)) { conn.Open(); using (var trans conn.BeginTransaction()) { using (var cmd new SQLiteCommand(INSERT INTO Demo (Name) VALUES (name), conn)) { cmd.Parameters.Add(name, System.Data.DbType.String); for (int i 0; i 5000; i) { cmd.Parameters[name].Value row_ i; cmd.ExecuteNonQuery(); } } trans.Commit(); } }关键点BeginTransaction之后所有插入都在同一个事务里最后一次性Commit。参数对象只创建一次循环里只改Value避免反复创建参数。这个改动通常能把 5000 条插入从几秒降到几十毫秒。注意事务不要开太久如果中间要处理 UI 消息记得分批提交否则会阻塞界面。4. 参数与配置连接字符串、并发和文件锁的边界4.1 连接字符串里真正需要调的三个参数System.Data.SQLite的连接字符串参数很多但日常真正需要动的就三个参数作用建议值Data Source数据库文件路径用绝对路径或明确的工作目录相对路径VersionSQLite 格式版本固定3Pooling是否启用连接池单线程上位机可设False多线程设TrueJournal Mode日志模式读多写少用WAL单写入用默认DeletePoolingTrue时连接关闭后不会真正释放而是放回池里下次打开更快。但在 SQLite 里连接池和文件锁有时会打架尤其是你手动删.db文件时会提示被占用。如果你在做单机上位机PoolingFalse更省心。Journal ModeWAL是 SQLite 的预写日志模式允许多个读和一个写同时进行适合读多写少的场景。设置方式是在连接字符串里加Journal ModeWAL;或者在打开连接后执行PRAGMA journal_modeWAL;。WAL 模式会额外生成-wal和-shm文件拷贝数据库时要一起拷否则可能丢数据。4.2 多线程上位机里 SQLite 的并发边界C# 上位机经常有多个线程串口接收线程、数据处理线程、UI 线程。如果多个线程同时写同一个 SQLite 数据库会抛database is locked。SQLite 本身支持多读单写但写锁是排他的。常见做法是所有写操作走一个专用队列或锁。简单点用lockprivate static readonly object dbLock new object(); public static void SafeInsert(string name) { lock (dbLock) { // 这里调用 ExecuteNonQuery } }更优雅的做法是用BlockingCollection做一个写入队列后台单线程消费。这样 UI 线程不会因为等锁而卡顿。注意lock只能保证同一进程内互斥如果你有多个进程同时写同一个.db文件SQLite 的文件锁会介入但性能会下降且容易超时。多进程场景建议每个进程写自己的库或者改用客户端-服务端数据库。4.3 数据库文件被占用、删不掉、拷不走的排查顺序现象程序关了但.db文件删不掉提示「文件正在被另一个程序使用」。原因通常是连接没有正确释放或者连接池还持有句柄。排查顺序确认所有SQLiteConnection都用了using或显式Dispose。如果用了PoolingTrue调用SQLiteConnection.ClearAllPools()清池。检查是否有SQLiteDataReader没关闭reader 不关会一直占着连接。如果用了 WAL 模式.db-wal和.db-shm文件也要一起处理。我一般会在程序退出时加一句SQLiteConnection.ClearAllPools()这个习惯帮我省了很多「文件被占用」的玄学问题。5. 避坑与常见问题Interop、加密库和路径的三个血泪教训5.1 现象Unable to load DLL SQLite.Interop.dll原因System.Data.SQLite是托管 dll它需要同目录下对应位数的原生SQLite.Interop.dll。AnyCPU 项目在 64 位系统上默认跑 64 位进程但如果输出目录只有 x86 的 Interop就会加载失败。解决在.csproj里显式指定平台或者用 NuGet 包System.Data.SQLite.Core它会把 x86 和 x64 的 Interop 都放到runtimes目录下自动加载。如果你用的是老开发包手动把x64/SQLite.Interop.dll拷到输出目录并确保项目平台设为 x64。5.2 现象打开数据库提示「file is encrypted or is not a database」原因开发包里的.db文件是用加密版 SQLite 创建的比如 SQLCipher 或System.Data.SQLite的加密版。普通System.Data.SQLite.dll没有解密能力打开就报这个错。解决确认开发包是否附带加密版 dll。如果是 SQLCipher需要引用SQLitePCLRaw.bundle_e_sqlcipher并设置Password参数。如果拿不到密码或加密库这个.db文件基本打不开只能找原始开发包或重新建库。这也是为什么我拿到开发包第一件事是看它有没有Password和加密 dll。5.3 现象程序在 IDE 里能跑双击 exe 就找不到数据库原因IDE 调试时工作目录是bin/Debug双击 exe 时工作目录是 exe 所在目录两者可能不同。如果连接字符串用的是相对路径数据库文件位置就会变。解决用AppDomain.CurrentDomain.BaseDirectory拼绝对路径string dbPath System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, data.db);这样无论从哪里启动数据库文件都在 exe 旁边。如果希望数据库放在用户目录用Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)。5.4 现象插入中文变成乱码或问号原因SQLite 默认用 UTF-8 存储文本C# 的string是 UTF-16。正常通过参数化插入不会乱码。乱码通常出现在用字符串拼接 SQL 且源文件编码不是 UTF-8 时。解决统一用参数化不要拼接。如果必须拼接确保.cs文件保存为 UTF-8 with BOM并在连接字符串里不需要额外设置编码。另外用 DB Browser for SQLite 打开.db文件查看时如果显示乱码先确认查看工具的编码设置不一定是数据本身的问题。5.5 现象SQLiteException: database is locked原因有另一个连接或进程正在写或者上一个事务没提交/回滚。WAL 模式下读不会阻塞写但写会阻塞写。解决检查是否有未释放的SQLiteCommand或SQLiteDataReader。给写操作加锁或队列。设置BusyTimeout让 SQLite 在锁冲突时等待而不是立刻报错string connStr Data Sourcedata.db;Version3;BusyTimeout5000;;BusyTimeout5000表示锁冲突时最多等 5 秒。这个参数在上位机频繁写入时很有用但不要设太大否则 UI 会卡。6. 进阶把开发包改造成可复用的 SQLite 访问层6.1 用 Dapper 简化查询但保留原生连接的控制权当你把原生 ADO.NET 跑通后可以引入 Dapper 来减少样板代码。Dapper 不是 ORM它只是IDbConnection的扩展方法底层还是SQLiteConnection。这样你既能用QueryT自动映射又能控制连接和事务。using Dapper; using System.Data.SQLite; public class DemoRow { public int Id { get; set; } public string Name { get; set; } } public static ListDemoRow GetAll() { using (var conn new SQLiteConnection(Data Sourcedata.db;Version3;)) { return conn.QueryDemoRow(SELECT Id, Name FROM Demo).AsList(); } }QueryDemoRow会把列名映射到属性名大小写不敏感。AsList()立即执行并返回ListT。注意 Dapper 不负责建表表结构还是自己维护。如果你的开发包实例源码里全是手写 reader改成 Dapper 后代码量能少一半但调试时要注意 SQL 错误信息还是来自 SQLite 本身。6.2 用 DB Browser for SQLite 验证数据别只信代码写完插入逻辑后不要只看控制台输出。用 DB Browser for SQLite 打开.db文件直接看表结构和数据。这个工具能帮你确认表是否真的建了、字段类型对不对、数据有没有写进去、WAL 文件是否正常合并。如果 DB Browser 打不开提示数据库被锁先关掉你的 C# 程序再打开。如果还是打不开检查.db文件是否 0 字节或者是否被加密。这个工具是 SQLite 开发里最值得装的辅助工具比在代码里Console.WriteLine高效得多。6.3 一个我坚持了多年的习惯先备份 .db 再改代码SQLite 开发最让人后悔的事就是改代码时把测试数据搞丢了或者一个DELETE没加WHERE把整张表清空。我的习惯是每次改涉及写操作的代码前先把.db文件复制一份命名带日期。如果用了 WAL 模式连.db-wal和.db-shm一起复制。另外在测试环境里不要用生产数据库文件。开发包里的实例数据库可以随便折腾但一旦接入真实上位机数据先建一个空库跑通流程再切换。这个习惯看起来笨但帮我省过好几次「数据没了」的麻烦。希望帮到你。本文还有配套的精品资源点击获取