news 2026/10/13 5:31:00

64位C#调用SQLite数据库源码:VS2012下增删改查与密码设置实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
64位C#调用SQLite数据库源码:VS2012下增删改查与密码设置实战

简介:这份资源面向使用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;Version=3;"; // 带密码连接(数据库已加密时) string connStrPwd = @"Data Source=.\data\history.db;Version=3;Password=MyPass123;";

参数说明:Data Source是库文件路径,相对路径以程序工作目录为基准,工控项目里我建议一律用AppDomain.CurrentDomain.BaseDirectory拼绝对路径,避免服务方式启动时工作目录不对。Version=3表示 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};Version=3;", 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};Version=3;", 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 Source=D:\app\data\new.db;Version=3;Password=InitPwd;"; 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};Version=3;Password={1};", srcPath, pwd); string dstConn = string.Format("Data Source={0};Version=3;", 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 加密,都强制走一遍「设密码 → 带密码读 → 不带密码读失败 → 导出明文 → 再设回去」的完整回路,确认每一步行为都符合预期才交付。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 5:30:29

iOS TableView异步加载图片:从卡顿到流畅的完整实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 5:30:12

运营基础知识全解析:从底层逻辑到实操体系搭建

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 5:28:39

显示驱动调试工具实战:逻辑分析仪、示波器与日志定位技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 5:28:22

Hitrate指标全解读:广告命中率、推荐召回与缓存命中率的区别与优化

“Hitrate”这个单词,放在不同人面前,理解完全不一样。做广告投放的同学会说这是“命中率”,做推荐算法的会说这是“召回命中率”,写后端的则会直接甩给你“缓存命中率”。我在这三个方向都折腾过,第一次被人问“你们H…

作者头像 李华
网站建设 2026/10/13 5:27:32

降压恒流LED电源芯片选型指南:从需求定义到实测避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 5:27:27

Gradle全量包gradle-7.2-all.zip构建确定性实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华