
简介这份资源面向使用C# 2012与VS2012开发64位Windows应用的开发者聚焦SQLite嵌入式数据库的调用与密码设置适合需要轻量级本地数据存储方案的中级学习者。包内共42个文件以cs源码、config配置、dll程序集、resx资源、exe可执行文件及sln解决方案为主压缩包约2.48MB结构完整可直接编译运行。项目围绕SQLiteConnection连接字符串、SQLiteCommand执行建表与增删改查、SQLiteDataReader读取结果等核心操作展开并演示在连接串中加入密码参数实现数据库加密保护帮助读者理解SQLite本身不直接支持库级密码时的替代思路。已有850人学习下载可作为C#桌面端数据管理、安全存储与VS2012工程配置的参考范例便于快速迁移到实际项目中。1. 从一次老项目维护说起这套 64 位 C# 调用 SQLite 的源码到底能干什么前阵子帮朋友收拾一个 2013 年上线的工控上位机机器是 Win7 64 位代码是 VS2012 写的 C#数据库用的是 SQLite。需求很朴素把历史报警记录从本地文件挪进数据库还要给数据库加个密码防止现场人员直接拿工具打开改数据。问题来了——网上搜到的 C# SQLite 示例十有八九是 32 位的System.Data.SQLite.dll扔进 64 位项目里一跑就报BadImageFormatException折腾半天才发现是位数不匹配。这套「64 位 C#2012 调用 SQLite 数据库源码含设置密码」正好踩在这个点上它给的是能在 VS2012 下直接编译的 64 位工程封装了增删改查还带了一个给 SQLite 数据库设置密码的完整流程。适合谁手上还有 VS2012 老项目要维护的 C# 上位机开发者、需要给本地库加访问口令的工控/医疗设备软件维护人员以及想搞清楚 SQLite 加密到底怎么落地的人。下面我按「先跑通、再讲参数、最后说坑」的顺序拆一遍。2. 环境搭起来VS2012 64 位 SQLite 的依赖选择与工程配置2.1 为什么必须是 64 位以及依赖包怎么选先说清楚一个容易翻车的点SQLite 本身是 C 写的但 .NET 这边用的是System.Data.SQLite这个托管封装它内部要 P/Invoke 一个原生SQLite.Interop.dll。这个原生 DLL 分 x86 和 x64 两个版本托管 DLL 会根据当前进程位数去加载对应目录下的 interop。如果你的 C# 工程编译目标是 AnyCPU在 64 位系统上默认跑成 64 位进程但引用的却是 32 位 interop运行时就会抛BadImageFormatException或者Unable to load DLL SQLite.Interop.dll。所以第一件事不是写代码是把位数对齐。选包的时候我一般直接去System.Data.SQLite的官方发布页拿带bundle的包比如System.Data.SQLite.dll加上x64、x86两个子目录的 interop。bundle 版的好处是托管和原生打包在一起省得自己配dll搜索路径。VS2012 对应的是 .NET Framework 4.5选包时注意别拿只支持 .NET Standard 的新版本那个在 VS2012 里根本引不进来。工程配置上右键项目 → 属性 → 生成 → 目标平台明确选x64不要留 AnyCPU。这一步是血泪经验AnyCPU 在开发机上可能碰巧能跑一到客户现场 32 位老机器上就崩。2.2 把 interop 放到正确位置一个能直接抄的目录结构很多人引完 DLL 就以为完事了结果一运行就找不到 interop。原因是System.Data.SQLite默认按x64\SQLite.Interop.dll和x86\SQLite.Interop.dll的相对路径去找。所以你的输出目录里必须有这两个子文件夹。我一般这样组织# 工程根目录下的 libs 文件夹放 SQLite 相关依赖 libs/ ├── System.Data.SQLite.dll ├── x64/ │ └── SQLite.Interop.dll └── x86/ └── SQLite.Interop.dll然后在 VS2012 里对这三个 DLL 分别设置「复制到输出目录 如果较新则复制」。注意x64和x86两个文件夹要作为内容保留目录结构不能只复制文件本身。如果你只做 64 位可以只留x64但保留x86对以后兼容没坏处。配置完编译一次去bin\x64\Debug下确认SQLite.Interop.dll确实在x64子目录里。这一步验证过了后面 90% 的「找不到 DLL」问题都不会出现。2.3 连接字符串怎么写密码参数放哪SQLite 的连接字符串和 SQL Server 那种不一样它没有独立的账号密码概念密码是通过连接字符串里的Password关键字传的。基础写法// 普通无密码连接 string connStr Data Source.\data\history.db;Version3;; // 带密码连接数据库已加密时 string connStrPwd Data Source.\data\history.db;Version3;PasswordMyPass123;;参数说明Data Source是库文件路径相对路径以程序工作目录为基准工控项目里我建议一律用AppDomain.CurrentDomain.BaseDirectory拼绝对路径避免服务方式启动时工作目录不对。Version3表示 SQLite 3 格式现在基本都是 3。Password就是加密口令注意这个口令一旦设置用普通 SQLite 工具比如 DB Browser for SQLite打开会提示「file is not a database」这是正常的因为文件头已经被加密了。提示Password只在数据库本身已加密或你要新建加密库时才有意义。对一个没加密的库直接带Password去连会报「file is encrypted or is not a database」别以为是密码错了。3. 核心代码拆解增删改查封装与设置密码的完整流程3.1 一个够用的 SQLiteHelper 封装源码里通常会给一个SQLiteHelper类把连接、执行、查询包起来。我按实际项目里最常用的形态还原一下核心结构public class SQLiteHelper { private readonly string _connStr; public SQLiteHelper(string dbPath, string password null) { // 拼接连接字符串密码为空时不带 Password 关键字 _connStr string.Format(Data Source{0};Version3;, dbPath); if (!string.IsNullOrEmpty(password)) _connStr Password password ;; } // 执行增删改返回受影响行数 public int ExecuteNonQuery(string sql, params SQLiteParameter[] ps) { using (var conn new SQLiteConnection(_connStr)) using (var cmd new SQLiteCommand(sql, conn)) { if (ps ! null ps.Length 0) cmd.Parameters.AddRange(ps); conn.Open(); return cmd.ExecuteNonQuery(); } } // 查询返回 DataTable适合小数据量 public DataTable ExecuteQuery(string sql, params SQLiteParameter[] ps) { using (var conn new SQLiteConnection(_connStr)) using (var cmd new SQLiteCommand(sql, conn)) using (var adapter new SQLiteDataAdapter(cmd)) { if (ps ! null ps.Length 0) cmd.Parameters.AddRange(ps); var dt new DataTable(); adapter.Fill(dt); return dt; } } }逻辑说明构造函数负责拼连接串密码可选ExecuteNonQuery用于 INSERT/UPDATE/DELETE用using保证连接释放ExecuteQuery用SQLiteDataAdapter填充DataTable适合工控里几百到几千行的配置表。参数说明params SQLiteParameter[]是可变参数调用时可以传零个或多个参数对象避免字符串拼接 SQL 带来的注入风险。调用示例var helper new SQLiteHelper(D:\app\data\history.db, MyPass123); // 建表 helper.ExecuteNonQuery(CREATE TABLE IF NOT EXISTS Alarm(Id INTEGER PRIMARY KEY, Msg TEXT, Ts DATETIME)); // 插入用参数占位 helper.ExecuteNonQuery(INSERT INTO Alarm(Msg, Ts) VALUES(msg, ts), new SQLiteParameter(msg, 温度超限), new SQLiteParameter(ts, DateTime.Now));3.2 给已有数据库设置密码ChangePassword 的用法这是这套源码的重点。SQLite 加密不是「打开一个开关」而是对数据库文件重新加密。System.Data.SQLite提供了SQLiteConnection.ChangePassword方法可以在连接打开后修改密码。流程是先用旧密码或空密码连上再调ChangePassword设新密码。public static void SetDatabasePassword(string dbPath, string oldPwd, string newPwd) { // 旧密码为空时连接串不带 Password string connStr string.Format(Data Source{0};Version3;, dbPath); if (!string.IsNullOrEmpty(oldPwd)) connStr Password oldPwd ;; using (var conn new SQLiteConnection(connStr)) { conn.Open(); // 关键在已打开的连接上改密码 conn.ChangePassword(newPwd); conn.Close(); } }逻辑说明ChangePassword必须在连接打开状态下调用它内部会重写数据库页并更新文件头。参数说明oldPwd是当前密码没设过就传null或空串newPwd是目标密码传空串表示取消加密。注意这个方法执行时间跟库大小相关十万条记录的库可能要几秒别放在 UI 线程里否则界面会卡成「未响应」。调用// 给一个没加密的库设密码 SetDatabasePassword(D:\app\data\history.db, null, MyPass123); // 改密码 SetDatabasePassword(D:\app\data\history.db, MyPass123, NewPass456); // 取消密码 SetDatabasePassword(D:\app\data\history.db, NewPass456, );3.3 建库时就带密码新建加密库的写法如果库还没建可以直接在连接串里带Password第一次Open时 SQLite 会按加密格式创建文件string connStr Data SourceD:\app\data\new.db;Version3;PasswordInitPwd;; using (var conn new SQLiteConnection(connStr)) { conn.Open(); using (var cmd new SQLiteCommand(CREATE TABLE Config(K TEXT, V TEXT), conn)) cmd.ExecuteNonQuery(); }这样建出来的new.db从第一页起就是加密的之后必须用InitPwd才能打开。参数说明Password长度没有硬性限制但工控现场我一般建议 8 到 16 位太短容易被猜太长现场人员记不住会来烦你。字符集上避免用中文和特殊符号老版本 SQLite 对非 ASCII 口令的处理在不同平台上有差异纯字母数字最稳。注意加密后的库不能用普通 SQLite 工具直接打开包括 DB Browser for SQLite。要查看内容得用带加密支持的连接方式或者先用代码把密码去掉再导出。4. 避坑与排查64 位 SQLite 在 VS2012 下最容易翻车的五件事4.1 现象运行报 BadImageFormatException 或找不到 SQLite.Interop.dll原因进程位数和 interop 位数不匹配或者x64子目录没被复制到输出目录。AnyCPU 在 64 位系统上跑成 64 位进程却加载了 32 位 interop就会报这个。解决项目目标平台明确设为x64确认bin\x64\Debug\x64\SQLite.Interop.dll存在如果用了安装部署项目把x64文件夹一并打包进去。别偷懒只复制System.Data.SQLite.dll。4.2 现象带密码连接报「file is encrypted or is not a database」原因对一个没加密的库传了Password或者密码输错。SQLite 加密后文件头被改写密码不对时它无法识别文件格式就报这个含糊的错误。解决先确认这个库到底有没有加密。判断方法是用不带Password的连接串去连如果能连上说明没加密那就别传密码如果连不上再试密码。密码错了没有更精确的提示这是 SQLite 加密的固有行为只能靠你自己记牢。4.3 现象ChangePassword 执行后程序卡死或超时原因ChangePassword会重写整个数据库文件数据量大时耗时明显放在 UI 线程就卡界面如果此时还有别的连接开着可能触发文件锁。解决改密码前确保没有其他连接在操作这个库把ChangePassword放到后台线程执行UI 上给个进度提示大库改密码前先备份一份万一中途断电文件可能损坏。4.4 现象32 位机器上部署后打不开库原因开发机是 64 位编译成 x64客户现场是 32 位 Win7进程根本起不来或加载 interop 失败。解决如果必须兼容 32 位就编译两个版本或者改回 AnyCPU 并同时带上x86和x64两个 interop 目录让运行时自己选。但 AnyCPU 有风险我一般直接问清楚现场系统位数编译对应版本省得运行期出玄学问题。4.5 现象数据库文件被现场人员用工具打开改了数据原因没设密码或者密码在配置文件里明文写着被翻出来了。解决给库设密码只是第一层连接串里的密码别硬编码在代码里至少做个简单混淆或放注册表更彻底的做法是业务层加校验字段比如关键记录带 HMAC被改了能发现。SQLite 加密防的是「随手打开看看」防不住有心人这点要有预期。5. 进阶把加密库和明文导出打通以及一个我常用的验证习惯实际维护中经常遇到一个尴尬库加密了但要做数据迁移、报表导出又需要明文。这时候不能直接拿加密库去喂第三方工具得先用代码解密导出。思路是用带密码的连接读数据写到另一个不带密码的库里。核心代码如下public static void ExportToPlain(string srcPath, string pwd, string dstPath) { string srcConn string.Format(Data Source{0};Version3;Password{1};, srcPath, pwd); string dstConn string.Format(Data Source{0};Version3;, dstPath); using (var src new SQLiteConnection(srcConn)) using (var dst new SQLiteConnection(dstConn)) { src.Open(); dst.Open(); // 用 ATTACH 把目标库挂到源连接上直接跨库复制 using (var cmd new SQLiteCommand( string.Format(ATTACH DATABASE {0} AS plain KEY ;, dstPath), src)) { cmd.ExecuteNonQuery(); } // 复制表结构和数据 using (var cmd new SQLiteCommand( CREATE TABLE plain.Alarm AS SELECT * FROM main.Alarm;, src)) { cmd.ExecuteNonQuery(); } } }逻辑说明ATTACH DATABASE ... AS plain KEY 把目标库以空密码挂载CREATE TABLE plain.Alarm AS SELECT * FROM main.Alarm完成跨库复制。参数说明KEY 表示目标库不加密如果目标库也要密码把空串换成目标密码即可。这个方法比逐行读出来再插进去快得多十万条记录基本秒级。验证方法上我有一个习惯每次改完密码或做完导出一定用两件事交叉验证——一是用代码带密码连一次能查到数据二是用不带密码的连接去连加密库确认它确实连不上。两个方向都对才说明加密状态符合预期。只测一个方向很容易出现「以为加密了其实没有」或者「以为没加密其实设上了」的乌龙。还有个细节System.Data.SQLite的加密实现和 SQLCipher 不是一回事两者生成的加密文件互不兼容。如果你的库以后可能要用 SQLCipher 生态的工具打开一开始就得选 SQLCipher 方案别用这套ChangePassword。反过来如果只是自己项目内部用这套足够依赖也简单。从那以后我每次给老项目加 SQLite 加密都强制走一遍「设密码 → 带密码读 → 不带密码读失败 → 导出明文 → 再设回去」的完整回路确认每一步行为都符合预期才交付。希望帮到你。本文还有配套的精品资源点击获取