☰
64位C#2012调用SQLite数据库并设置密码的完整源码
2026/10/2 0:23:49 网站建设 项目流程

简介:这份资源面向64位Windows环境下使用C#语言配合Visual Studio 2012开发工具调用SQLite数据库的开发者,着重演示如何基于System.Data.SQLite组件完成创建数据库、建立数据表、插入数据、查询数据等常用操作,并讲解在连接字符串中设置密码以保护数据库文件的安全做法。资源包共包含42个文件,以C#源代码、项目配置文件、可执行程序以及调试符号文件为主要类型,压缩包大小为2.48MB,目录结构清晰,便于开发者对照学习。目前已有850人学习下载。通过该工程,读者可以查看完整的窗体界面源码,掌握带密码参数的SQLite连接字符串写法,了解SQLiteCommand参数化增删改查和SQLiteDataReader读取结果集的具体实现,同时还能参考SQLitePassWord项目文件中关于密码保护的组织方式。适合初、中级C#开发者快速上手轻量级嵌入式数据库的应用开发。

1. 64位C#2012调用SQLite数据库:这套源码真正难的是密码和进程位数

VS2012 里写 C# 上位机,数据要落到本地文件,最顺手的选择就是 SQLite 数据库:免安装、单文件、标准 SQL 语法。但真把工程切到 64 位、再想给库文件加个密码时,很多人会发现“连上了”和“跑对了”是两回事——System.Data.SQLite.dll 和 SQLite.Interop.dll 的分工、x64 目录结构、密码 API 的执行时机,随便一个环节不对就是运行时翻车。这篇文章把 64 位 C#2012 调用 SQLite 的完整源码思路拆开讲:从选型、建库、增删改查到给数据库设置密码并验证密码真的生效,照着搭就能直接跑。

2. 选型:System.Data.SQLite 的三种调用方式与 64 位部署形态

2.1 C# 里调用 SQLite 的三条路:ADO.NET、官方包、原生 P/Invoke

C# 侧接 SQLite 从来不止一种方式,但适合 VS2012 工程的并不多。第一条路是 ADO.NET 提供程序 System.Data.SQLite,它实现了 SqlConnection 那套接口规范,老项目迁移成本最低,也是这套源码采用的方式。第二条路是微软官方的 Microsoft.Data.Sqlite,它在 .NET Core 时代才出现,VS2012 默认的 .NET Framework 4.5 工程用不了,直接排除。第三条路是 P/Invoke 直接调 sqlite3.dll,需要自己封装大量 C API,而且官方 sqlite3.dll 不带加密集,密码功能还得额外找 SQLCipher 编译版本,工程量大得没必要。

对于“64 位 C#2012 调用 SQLite 数据库源码,含设置密码”这个诉求,System.Data.SQLite 几乎是唯一能同时满足几个条件的方案:兼容 .NET 4.5、原生支持 x64 部署、内部自带加密 API。它在 NuGet 上的包全名是 System.Data.SQLite.Core,我一般只装 Core 包,不带设计时组件。

2.2 两个 DLL 的分工:System.Data.SQLite.dll 与 SQLite.Interop.dll

很多第一次做 64 位 SQLite 的人只拷了一个 System.Data.SQLite.dll 到 bin 目录,然后运行时报错找不到原生库。这不是玄学,是 System.Data.SQLite 的架构决定的:System.Data.SQLite.dll 是托管壳层,负责 ADO.NET 连接管理、SQL 语句调度和类型转换;真正执行 SQL、读写页文件的是原生 DLL SQLite.Interop.dll。托管壳只是把调用转发给原生引擎。

更重要的是加载规则。System.Data.SQLite.dll 会根据当前进程位数,去应用目录下的 x86 或 x64 子目录里寻找对应位数的 SQLite.Interop.dll。也就是说它的目录结构必须是这个形态:

bin\ System.Data.SQLite.dll x86\SQLite.Interop.dll x64\SQLite.Interop.dll

项目跑在 64 位进程里,就去 x64\SQLite.Interop.dll;跑在 32 位进程里,就去 x86\SQLite.Interop.dll。如果只把原生 DLL 放在根目录、或者忘了建子目录,System.Data.SQLite.dll 会直接抛找不到文件。这也是 64 位部署时最容易踩的一步。

2.3 用 NuGet 在 VS2012 里安装并确认目录结构

VS2012 自带 NuGet 包管理器,安装操作很直接:

Install-Package System.Data.SQLite.Core -Version 1.0.96.0

版本号不一定要卡这个,装最新兼容 .NET 4.5 的即可。安装完,NuGet 会自动处理三件事:把 System.Data.SQLite.dll 放到输出目录根、把 x86\x64 两个子目录连同 SQLite.Interop.dll 一起拷进 bin、并把 Copy Local 属性设置好。装完建议立刻检查输出目录,这个动作在后续排错里经常能救命。

从这条命令往后,整个工程引用的就是这套“托管壳 + 原生引擎”组合。在这个基础上做的连接、读写、加密代码,跟直接用原始 sqlite3.dll 是完全不同的体验:你可以用 SqliteConnection、SqliteCommand 这种标准 ADO.NET 对象写业务逻辑,而不是手推指针和回调。

3. 主流程源码:从连接串到增删改查的完整可运行代码

3.1 连接串与建库:版本、路径、超时参数

先把最小的可运行代码立起来,后面所有加密逻辑都挂在这套连接之上。我一般会新建一个控制台工程,目标框架选 .NET 4.5,然后写入口代码:

using System; using System.Data.SQLite; namespace SQLiteDemo { class Program { static void Main(string[] args) { // 数据库文件放在程序目录下,避免工作目录不一致导致找不到文件 string dbPath = AppDomain.CurrentDomain.BaseDirectory + "app.db"; string connStr = "Data Source=" + dbPath + ";Version=3;Default Timeout=30;"; using (SQLiteConnection conn = new SQLiteConnection(connStr)) { conn.Open(); string createSql = @"CREATE TABLE IF NOT EXISTS t_meter( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, value REAL)"; using (SQLiteCommand cmd = new SQLiteCommand(createSql, conn)) { cmd.ExecuteNonQuery(); } Console.WriteLine("数据库创建成功:" + dbPath); } } } }

连接串里的三个参数分别有讲究。Data Source 指定库文件路径,推荐拼 BaseDirectory 绝对路径,防止调试时当前目录漂移;Version=3 是固定写法,SQLite 3.x 版本都这样写;Default Timeout=30 是命令执行超时,单位秒,遇到锁竞争时这个参数比想象中更重要。建表语句加了 IF NOT EXISTS,重复运行不会报错。AUTOINCREMENT 不是必需的,但如果你需要保证 id 严格递增且不重用,就保留它。

这里有个容易犯的错:有人把连接串写成DataSource=而不是Data Source=,中间少个空格。System.Data.SQLite 对老写法的兼容时好时坏,与其赌兼容性,不如直接用带空格的规范写法。

3.2 参数化增删改查与类型映射

连接能打开只是第一步,业务代码迟早要处理增删改查。下面这段是参数化写法和读取的类型映射,也是这套源码里被复用最多的模板:

using (SQLiteConnection conn = new SQLiteConnection(connStr)) { conn.Open(); // 插入:参数化是必须的,杜绝拼接 SQL string insertSql = "INSERT INTO t_meter(name, value) VALUES(@name, @value)"; using (SQLiteCommand cmd = new SQLiteCommand(insertSql, conn)) { cmd.Parameters.AddWithValue("@name", "A相电压"); cmd.Parameters.AddWithValue("@value", 220.5); int affected = cmd.ExecuteNonQuery(); Console.WriteLine("插入行数:" + affected); } // 查询:条件也参数化 string selectSql = "SELECT id, name, value FROM t_meter WHERE value > @min"; using (SQLiteCommand cmd = new SQLiteCommand(selectSql, conn)) { cmd.Parameters.AddWithValue("@min", 200.0); using (SQLiteDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { // SQLite 的 INTEGER 映射到 C# 的 long long id = (long)reader["id"]; string name = reader["name"].ToString(); // REAL 映射到 double double value = (double)reader["value"]; Console.WriteLine($"{id} | {name} | {value}"); } } } }

关于 AddWithValue,有两个细节值得注意。第一,传 null 时要用DBNull.Value,否则行为不确定;第二,高频写入场景不建议用 AddWithValue,因为参数类型每次都推断,有开销,建议改成cmd.Parameters.Add("@value", DbType.Double).Value = value。读取侧的映射规则是固定的:INTEGER 类型读出来是 long,REAL 是 double,TEXT 是 string,BLOB 是 byte[]。如果库里存的是INTEGER你却用(int)reader["id"]转换,会抛 InvalidCastException,这就是为什么上面的代码用 long 接。

3.3 平台目标设为 x64 并正确部署原生 DLL

代码层面没问题后,还要让程序真跑在 64 位进程里。VS2012 的操作路径是:项目属性 → 生成 → 平台目标 → 选 x64,然后重新生成解决方案。

为什么不建议用默认的 AnyCPU?VS2012 的 AnyCPU 在 64 位系统上默认就是 64 位进程,表面看也能跑,但有两个隐患:一是后续迁移到高版本 Visual Studio 时,项目可能会被自动勾选“首选 32 位”,进程位数悄悄变回 x86;二是 System.Data.SQLite.dll 内部通过Environment.Is64BitProcess决定加载哪个 Interop,AnyCPU 加上某些第三方 32 位组件的引用后,容易整体降级成 32 位。直接锁死 x64,行为最可预期。

部署时只需要确认输出目录里同时存在三件东西:

bin\x64\ System.Data.SQLite.dll(实际引用的是根目录那份托管 DLL) SQLite.Interop.dll(原生引擎,x64 版) bin\项目.exe bin\System.Data.SQLite.dll

验证是否真的进了 64 位,可以在入口打一行:Console.WriteLine(Environment.Is64BitProcess);,输出 True 就说明 x64 部署成功。这一步是后面所有密码功能的前提,位数不对,加密库的读写行为都会变得不可捉摸。

4. 给 SQLite 设置密码:加密库创建、改密与真实性验证

4.1 新建数据库时直接创建加密库

先说清楚一个前提:SQLite 官方版本本身没有密码机制,密码功能是 System.Data.SQLite 这个发行版自己实现的页级加密扩展。它跟 SQLCipher 不是同一套格式,所以“SQLCipher 的库拿到 System.Data.SQLite 里开”或者反过来,都打不开。这套源码里的密码都基于 System.Data.SQLite 的 API,用同族工具链来处理即可。

新建加密库非常简单,连接串里加一个 Password 参数:

string dbPath = AppDomain.CurrentDomain.BaseDirectory + "encrypted.db"; // 关键点:建库时带上 Password,生成的库文件本身就是加密的 string connStr = "Data Source=" + dbPath + ";Version=3;Password=MyPass123;"; using (SQLiteConnection conn = new SQLiteConnection(connStr)) { conn.Open(); string createSql = "CREATE TABLE IF NOT EXISTS t_secret(k TEXT, v TEXT)"; using (SQLiteCommand cmd = new SQLiteCommand(createSql, conn)) { cmd.ExecuteNonQuery(); } Console.WriteLine("加密库创建成功,文件头已不是明文"); }

这段代码运行后,磁盘上的 encrypted.db 文件头不再是 “SQLite format 3” 这串明文,而是一堆不可读的密文。从这一刻起,任何不带密码的连接字符串去打开这个库,都会抛“file is encrypted or is not a database”异常。这个行为本身可以直接当作加密是否生效的试金石。

4.2 给已有明文库补上密码:ChangePassword 的用法与时机

更多的现实场景是手里已经有一个跑了好久的明文库,想在不丢失数据的前提下加上密码。这时不能只改连接串,因为连接串里的 Password 只在“新建加密库”或者“连接加密库”时起作用;对一个已存在的明文库,连接串写 Password 并不能让它变成加密库。必须调用 ChangePassword:

string oldDb = AppDomain.CurrentDomain.BaseDirectory + "plain.db"; string connStr = "Data Source=" + oldDb + ";Version=3;"; using (SQLiteConnection conn = new SQLiteConnection(connStr)) { conn.Open(); // 把当前打开的明文库立即重写为加密库 conn.ChangePassword("MyPass123"); Console.WriteLine("明文库已转换为加密库"); }

ChangePassword 是 SQLiteConnection 的实例方法,调用前提是连接已经处于 Open 状态,而且在它之后不要再执行任何其它事务操作。它的执行时机很关键:连上明文库后第一时间调用,确保所有页都带着密钥重写回文件。修改已有加密库的密码也是同一个方法:用旧密码打开连接,再调用 ChangePassword 传新密码,旧密钥就被替换成新密钥。

如果你想反过来把加密库恢复成明文库,调用conn.ChangePassword(null)即可。这个操作会把整个库重写为无密码文件,相当于把密码功能彻底拆掉。

4.3 验证密码真的生效:文件头检查与错误密码试连

设置密码之后不能只看“没报错”就认为完成,我用两个手段验证,缺一不可。

第一个手段是检查文件头。SQLite 明文库的前 16 个字节固定是 “SQLite format 3\0”,被 System.Data.SQLite 加密后的文件,这 16 个字节变成了密文。写一个 5 行的判断函数就行:

static bool IsPlainSqlite(string path) { byte[] header = new byte[16]; using (System.IO.FileStream fs = new System.IO.FileStream(path, System.IO.FileMode.Open)) { fs.Read(header, 0, 16); } return System.Text.Encoding.ASCII.GetString(header).StartsWith("SQLite format 3"); }

加密库返回 False,明文库返回 True,这个函数肉眼可验证,比任何解释都有说服力。

第二个手段是用错误密码去连一次,确认系统真的会拒绝:

try { using (SQLiteConnection conn = new SQLiteConnection( "Data Source=" + dbPath + ";Password=WrongPass;")) { conn.Open(); } } catch (SQLiteException ex) { // 数据库是加密的,密码错误时这里必然走进来 Console.WriteLine("错误密码被拒绝:" + ex.Message); }

这里要提醒一个常见误区:DB Browser for SQLite 虽然能打开带密码的库,但它支持的加密格式跟 System.Data.SQLite 不通用。你用 DB Browser 输密码去开 System.Data.SQLite 加密出来的库,它大概率直接提示文件损坏或无法识别,这不代表密码设置失败,只是格式不同。验证加密是否生效,优先用上面两个手段,不要拿非同类工具下结论。

5. 常见问题与避坑:64位 SQLite 在 VS2012 里最容易翻车的五个地方

5.1 运行时报错找不到 SQLite.Interop.dll

现象是程序编译通过,一运行就抛异常:Unable to load DLL 'SQLite.Interop.dll',或者Could not load file or assembly ... SQLite.Interop.dll。原因是部署结构不对——System.Data.SQLite.dll 作为托管壳被找到了,但它加载原生引擎时按进程位数去 x64 或 x86 子目录找,子目录不存在,直接失败。

解决办法是重新建立标准目录结构,把原生 DLL 放回对应子目录:

bin\System.Data.SQLite.dll bin\x64\SQLite.Interop.dll bin\x86\SQLite.Interop.dll

处理完记得重新生成工程,确认这两个子目录真的出现在输出路径里。如果用的是手动引用 DLL 的部署方式(比如从别的机器上拷 dll),最容易漏的就是子目录结构,我做过一次这种事:根目录文件全齐,唯独少了 x64 子文件夹,排查了半小时。血的教训:先看输出目录,再查代码。

5.2 Mixed Mode Assembly 报错:.NET 运行时版本对不上

现象:引用 System.Data.SQLite 后,程序一启动就抛Mixed mode assembly is built against version 'v2.0.50727' of the runtime。原因是 System.Data.SQLite.dll 本身是混合模式程序集,内部同时包含托管 IL 和原生代码,部分版本是按 .NET 2.0 目标编译的。当宿主工程跑在 .NET 4.x 上而没有显式声明兼容策略时,就会出现这个错误。

解决方式是在 app.config 里加一段配置:

<?xml version="1.0" encoding="utf-8"?> <configuration> <startup useLegacyV2RuntimeActivationPolicy="true" /> </configuration>

useLegacyV2RuntimeActivationPolicy 的意义是允许 CLR 2.0 的混合模式程序集加载进 CLR 4.x 运行时。加了它之后绝大多数旧版 System.Data.SQLite 都能正常跑。如果加了还报错,就优先升级 NuGet 包,新版包已经没有这个问题,不要从网上下载来路不明的旧版 dll 硬顶。

5.3 密码设了却还能被明文工具直接打开

现象:代码里写了 Password,库文件也能正常打开,但用户用别的工具一看,数据全是明文。这个坑分两种情况。

第一种情况,你只是改了连接串,没有对已存在的库执行 ChangePassword。连接串里的 Password 只负责“新建加密库”和“连接加密库”,它不具备把明文库就地加密的能力。处理方式是先判断库是不是明文,是明文就主动调 ChangePassword。

第二种情况隐蔽得多:库开了 WAL 模式,加密前未执行 checkpoint,导致明文页残留在 -wal 文件中。SQLite 的 WAL 模式会把新写入数据先追加到同名 .wal 文件,ChangePassword 加密的是主库文件,遗漏的 -wal 文件里还留着加密前的原始页。之后正常打开库,WAL 重放,部分页面绕过了加密读取。

解决思路是加密操作前强制切回 DELETE 日志模式并做一次 checkpoint:

// 加密前先把 WAL 写回主库并清理 wal/shm 文件 using (SQLiteCommand cmd = new SQLiteCommand("PRAGMA journal_mode=DELETE;", conn)) { cmd.ExecuteNonQuery(); } // 然后执行 ChangePassword conn.ChangePassword("MyPass123");

执行完这句 PRAGMA,旧事务日志被合并回收,再加密,才真正做到全文件加密。没有这条经验的人很容易把“数据库文件加密了”误当成“整个库都安全了”,实际上是主库密文、日志明文的状态。

5.4 高并发写入时报 database is locked

现象:多线程或多进程同时写入时,第二个写连接等待超时后抛出database is locked。原因不是密码功能的问题,而是 SQLite 本身的锁模型:同一时间只允许一个写事务,其它写请求必须等待;默认 busy timeout 又很短,等不到锁就被判定超时。

常规处理有两手:连接串加长时间,同时显式设置 busy_timeout:

Data Source=app.db;Version=3;Default Timeout=30;
using (SQLiteCommand cmd = new SQLiteCommand("PRAGMA busy_timeout=5000;", conn)) { cmd.ExecuteNonQuery(); }

这两行组合的语义是:遇到锁冲突时最多等 5 秒,而不是立即放弃。此外检查一下代码里有没有把 SQLiteDataReader 还开着就去执行另一个写操作,这种嵌套持有连接的情况也会触发锁。锁定问题排查时先看代码结构,再看超时参数,绝大多数是前者。

5.5 确认进程真的以 64 位在跑

现象:代码逻辑完全一致,但 SQLite 行为异常,或者加载的 DLL 版本混乱。原因可能是平台目标设置没生效,或者工程被另一个 x86 工程引用,最终进程还是 32 位。判断方法不要靠猜,直接打一行:

Console.WriteLine("Is64BitProcess = " + Environment.Is64BitProcess); Console.WriteLine("IntPtr.Size = " + IntPtr.Size);

64 位进程输出 True、8;32 位进程输出 False、4。如果输出是 False 但项目属性显示 x64,检查一下是不是最终启动入口工程被引用链降级了。这套源码的后续所有功能,包括加密 API 的参数传递,都要求进程位数和 SQLite.Interop.dll 位数严格一致。进程位数是 x64、用的却是 x86 子目录里的 Interop,系统加载时一样会报错。先确认位数,再查其它问题,能省掉一半的排查时间。

6. 进阶:加密库进入生产环境前要处理的三个细节

6.1 加密库慎用 WAL 模式

上面第 5.3 节提到过 WAL 模式会留下 -wal 文件,这个隐患在生产环境里不仅是加密时机的问题。即使加密库已经稳定运行,只要 journal_mode 是 WAL,每次写入都会产生一个 -wal 文件,而这个文件里的页在部分场景下是明文。对于“含设置密码”的库,我把默认日志模式固定为 DELETE,数据写入时时落回主库,不从文件结构上留尾巴。如果实在需要 WAL 的并发读性能,至少对 -wal 文件做同等权限管控,并定期执行 checkpoint 强制合并。

6.2 备份加密库用 BackupDatabase,不要直接拷文件

数据库文件在打开状态下直接复制,得到的一致性和完整性都没有保证。System.Data.SQLite 提供了封装好的备份方法:

using (SQLiteConnection dest = new SQLiteConnection("Data Source=backup.db")) { dest.Open(); // 把源数据库的 main schema 备份到目标库 conn.BackupDatabase(dest, "main", "main"); }

这个方法走的是 SQLite 官方备份 API,能保证页级一致性。备份出来的库同样继承源库的加密状态,需要用密码连接才能读取。养成用 API 备份的习惯后,我就不再干“先关程序再复制文件”这种原始操作了,少了很多莫名奇妙的文件损坏。

6.3 改密码后清掉连接池里的旧连接

System.Data.SQLite 默认启用连接池。改密码后,池里可能还残留着旧密钥打开的连接。一个新的业务连接如果被池里复用了旧连接,用的还是旧密钥,一旦数据库侧密钥已经轮换,就会出现“密码改了,老连接还能访问”的诡异现象。轮换密码后加一行清理:

SQLiteConnection.ClearPool(conn);

这个动作会把该连接串对应的池清空,后续连接全部按新密码重新建立。密码轮换是一件低频但关键的操作,舍得花这一行代码,线上就能少一次莫名其妙的数据访问异常。

我现在接手 SQLite 相关的工程,习惯是先把目录结构、进程位数、加密状态一次确认完,再写业务代码;这套源码如果从一开始就按这个顺序做,后面几乎所有坑都能绕开。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询