
简介在.NET Framework 4.0项目中集成SQLite时32位与64位程序集的选择常令开发者头疼尤其是引用错误导致的“试图加载格式不正确的程序”问题。该压缩包内置sqlite-netFx40-binary-bundle的Win32与x64两套组件包含System.Data.SQLite.dll及EF6、LINQ扩展支持通过ADO.NET的SQLiteConnection、SQLiteCommand、SQLiteDataReader等对象直接操作数据库也可使用SQLiteDataAdapter与DataSet完成内存数据管理配合SQLiteTransaction保证事务一致性并支持Entity Framework的Code First/Database First开发模式覆盖建表、查询、迁移等常见开发场景兼顾事务、异步、索引优化等常用特性适用于桌面应用、离线存储与轻量级业务系统。包体共50个文件以dll、pdb、xml、config为主还附带exe演示程序、可视化设计器组件与northwindEF.db测试库目录区分Win32与x64避免选错程序集dll为运行核心pdb便于调试定位xml提供API注释config可灵活调整连接设置方便快速集成与排错。整个压缩包仅4.26MB已有440人学习下载适合需要快速接入SQLite并兼顾x86/x64部署的.NET开发者。1. 先搞清楚 sqlite-netFx40-2010 到底是什么再决定要不要用它很多老程序员手里都存着这个sqlite-netFx40-2010.rar可能是当年从论坛或同事的网盘上转下来的放了好几年突然要在新项目里连接 SQLite又把它翻出来。我先说结论这是一个针对 .NET Framework 4.0 和 Visual Studio 2010 时代打包的 SQLite ADO.NET 数据访问组件里面核心是两个东西System.Data.SQLite.dll和SQLite.Interop.dll。前者是托管代码帮你把 ADO.NET 接口接上后者是原生 C/C 编译的 SQLite 引擎本身会根据进程架构区分为 32 位版和 64 位版。这个库解决的问题很朴素让 C#、VB.NET 这类 .NET 程序能用标准的方式访问 SQLite 数据库。SQLite 本身是嵌入式关系型数据库程序直接读写一个.db文件不需要服务端进程不需要账号密码也不用像 Access 那样额外装什么驱动。它特别适合做小工具、内部管理系统、离线数据缓存、单机报表这类场景。我自己做过不少 WinForms 上位机数据库这块从 Access 换到 SQLite 之后分发部署轻松了一个量级——拷一个 exe 加一个 db 文件就能跑。那 2010 年的老组件为什么现在还值得聊因为现实中大量生产环境还跑在 Windows 7、Windows Server 2008 R2 甚至是 XP 上.NET 4.8 虽然能装到 Windows 7 SP1但如果你要维护一个从来没升级过的老工程sqlite-netFx40这套基于 .NET 4.0 的库反而兼容性非常稳。它不是考古是很多存量项目里真实存在的依赖项。不过这里要强调一下后面的内容不会只讲“引用一下 DB 就能用了”。SQLite 的坑多半不在 SQL 语法上而在 DLL 架构和部署路径上这也是我写这篇的主要原因。2. 32 位和 64 位真正的决定权不在“项目平台”而在“进程平台”这个点我见过太多人栽跟头了。很多读者一看到标题里的“32位和64位”第一反应就是“我项目改成 x64 不就行了”实际上问题没有这么简单。.NET 项目有个概念叫AnyCPU也就是编译出来的程序集不限定 CPU 架构CLR 加载它时再决定按 x86 还是 x64 运行。关键规则是运行在 64 位系统上默认情况下 AnyCPU 进程会以 64 位运行如果项目启用了“Prefer 32-bit”Visual Studio 2012 及之后在编译选项里默认勾选则任何系统上都会以 32 位运行如果你把项目平台改成 x86那即便在 64 位 Windows 上进程也固定为 32 位。SQLite 的SQLite.Interop.dll是原生 DLL它不会自动“跨架构”x86 版无法被 64 位进程加载x64 版也无法被 32 位进程加载。所以真正决定你要放哪个 DLL 的不是“我装的是 64 位系统”而是“这个进程最终运行在什么架构上”。我习惯在下单或部署前先用日志把进程架构打出来验证Console.WriteLine(Is64BitProcess Environment.Is64BitProcess); Console.WriteLine(Is64BitOperatingSystem Environment.Is64BitOperatingSystem);这两行代码能在前 10 秒就排查掉一大半问题。如果输出是Is64BitProcess False说明进程是 32 位那你就应该让程序目录下有x86\SQLite.Interop.dll而不是x64\SQLite.Interop.dll。下面这张表是我在实际项目中常用来给同事解释的对照关系建议直接收藏项目平台系统位数进程位数需要提供的 InteropAnyCPU 且不勾 Prefer 32-bitx64x64x64AnyCPU 且勾选 Prefer 32-bitx64x86x86x86x64x86x86x64x64x64x64AnyCPUx86x86x86也就是说一个“AnyCPU Dll x86/x64 双目录”的方案是可以同时覆盖 32 位和 64 位系统的条件是你的程序让System.Data.SQLite.dll自己去选择加载哪个原生的 SQLite 引擎。很多老开发者把这个东西想复杂了其实它并没有一个程序只配一个 DLL 的限制反倒是你把 Interop 直接丢在根目录下会因为路径不对而加载失败。3. 完整的接入步骤从解压 rar 到跑通一条 SQL如果你确定要沿用这套老库我建议按下面这个顺序做每步都不白做。3.1 工程目录结构解压sqlite-netFx40-2010.rar后一般会得到一个类似这样的目录骨架Sqlite-netFx40/ ├── System.Data.SQLite.dll ├── System.Data.SQLite.Linq.dll ├── x86/ │ └── SQLite.Interop.dll └── x64/ └── SQLite.Interop.dll如果压缩包里的结构不是这样你也要尽量凑成这个结构因为这是System.Data.SQLite.dll查找原生引擎的默认路径。它会在进程目录下找x86或x64子目录再按当前进程位数加载对应SQLite.Interop.dll。3.2 添加引用并设置 Copy Local在 Visual Studio 里右键“引用”添加System.Data.SQLite.dll。如果你的项目里还需要 LINQ 到 SQLite 的查询能力可以一并引用System.Data.SQLite.Linq.dll。添加完成后把这两个托管 DLL 的“复制到输出目录”属性设为Copy if newer或Copy always。这一步的关键是很多人的SQLite.Interop.dll文件没被复制过去。因为它在x86、x64子目录里VS 是不会自动帮你处理这种情况的。要么你把 x86/x64 目录一起拷到输出目录要么在项目里加一个“把 x86/x64 目录复制到输出目录”的生成事件。我个人更推荐直接在项目文件里把这两个目录包含进来并确保它们始终复制。3.3 配置连接字符串在App.config或Web.config里加一段如下配置configuration connectionStrings add nameDefault connectionStringData Sourceapp.db;Version3;PoolingTrue;FailIfMissingFalse providerNameSystem.Data.SQLite / /connectionStrings /configuration这里说明一下参数含义Data Source是数据库文件路径支持绝对路径和相对路径Version3指定 SQLite 数据库文件版本通常固定写 3PoolingTrue会开启连接池对频繁打开关闭的小工具性能有正面帮助FailIfMissingFalse表示文件不存在时不会直接抛异常便于程序首次启动时自动建库。3.4 写一段最小可用代码如果你不想通过ConfigurationManager读取连接串直接写在代码里也完全可以using System.Data.SQLite; using (SQLiteConnection conn new SQLiteConnection(Data Sourceapp.db;Version3;)) { conn.Open(); using (SQLiteCommand cmd new SQLiteCommand( SELECT id, name FROM users WHERE status status, conn)) { cmd.Parameters.AddWithValue(status, active); using (var reader cmd.ExecuteReader()) { while (reader.Read()) { int id reader.GetInt32(0); string name reader.GetString(1); Console.WriteLine(${id}: {name}); } } } }注意我在这里用了参数化查询没有拼字符串。SQLite 和很多数据库一样千万别把外部输入直接拼进 SQL这不是“老项目不讲究”的借口。还有SQLiteConnection、SQLiteCommand这些对象都实现了IDisposable用using包起来是标准姿势。3.5 一个很容易踩的更新语法坑近年来很多人搜“sqlite 存在就更新不存在就新增”这个在 SQLite 里要用 UPSERT 语法。比较新的 SQLite 版本支持INSERT INTO users(id, name) VALUES(id, name) ON CONFLICT(id) DO UPDATE SET name name;但这个语法要求 SQLite 引擎版本够新。sqlite-netFx40-2010里带的引擎已经是很多年前的了如果你发现老引擎不认识ON CONFLICT建议要么改用“先 UPDATE如果影响行数为 0 再 INSERT”的兼容写法要么把组件升级到新版本别硬扛老语法。4. 部署现场最容易翻车的三个点以及完整排查链路跑通本地 Demo 不算什么真正考验人的是换一台机器、或者换一个服务器之后“能编译但跑不起来”。这一章我把常见问题从错误现象到定位过程完整捋一遍。4.1 报错Unable to load DLL SQLite.Interop.dll: 找不到指定的模块这个报错的根因通常是文件没到位或者找到了但依赖环境不对。不要一上来就重装什么 C 运行库先做三步定位打开程序输出目录看有没有x86、x64两个子目录确认这两个目录里的SQLite.Interop.dll确实存在用dumpbin /headers SQLite.Interop.dll查看机器类型确认是 x86 还是 x64。没有 dumpbin 也可以用 Dependencies 这类图形工具加载 DLL 之后看目标机器一栏。如果目录结构都对但依然报这个错多半是 64 位进程去加载了 32 位 DLL或者反过来。这时候再看一眼Environment.Is64BitProcess的输出就能立刻缩小范围。4.2 报错Could not load file or assembly ... 试图加载格式不正确的程序这句话的英文通常是BadImageFormatException。它比“找不到模块”更明确CLR 想加载一个程序集或原生 DLL但文件架构和当前进程架构不匹配。我处理过一个典型的现场运维把应用发布到 Windows Server 2008 R2 上本机测试没问题服务器一跑就报这个。后来发现程序编译时选的是 x64服务器的 IIS 应用池默认“启用 32 位应用程序”为 False理论上应该没问题。但服务器上部署人员误把x86\SQLite.Interop.dll覆盖到了x64文件夹里导致进程用 x64 去加载 x86 DLL。所以排查这个错误时不要只看目录在不在要直接核对每个 DLL 的真实架构。最直接的命令是dumpbin /headers x64\SQLite.Interop.dll | findstr machine如果显示x86那就是文件放错位置了。下面这张错误对照表基本能覆盖我遇到过的 80% 架构问题现象可能原因处理方向BadImageFormatException32 位 DLL 被 64 位进程加载或反之按进程位数选择对应的 x86/x64 DLL找不到 SQLite.Interop.dll子目录缺失或未复制到输出目录确保 x86/x64 目录随程序一起部署本机能跑服务器不能跑服务器进程位数与开发机不同IIS 应用池设置不一致在入口处打印进程位数检查应用池“启用 32 位应用程序”打开数据库提示“file is not a database”数据库路径写错或文件不是 SQLite 格式用 DB Browser for SQLite 打开看文件头确认是 SQLite 3 格式4.3 注意数据库文件本身没有“位数”一说还有一个和位数无关、但经常被混在一起的问题.db文件本身不区分 32 位和 64 位你说“数据库是 64 位”这个说法在 SQLite 里不成立。真正有位数的是SQLite.Interop.dll是驱动层的东西。很多从 Access 转过来的人会把“请先安装 access 数据库 64 位系统驱动程序”这种经验带进来下意识觉得数据库文件也需要对应位数。SQLite 不需要无论你系统是 32 位还是 64 位访问同一个.db文件都可以只要驱动 DLL 和进程架构匹配就行。这个认知一旦纠正过来后面很多部署问题都能想通。4.4 建议用 DB Browser for SQLite 验证数据库文件当程序报错“no such table”或“file is not a database”时别急着查代码先用 DB Browser for SQLite 打开那个.db文件能看到表结构就说明文件本身没问题问题在路径、连接串或者代码逻辑上。我用这个工具主要干三件事快速确认表名、列名有没有拼错用图形界面执行一条 SQL验证语法是否被当前 SQLite 版本支持导出一份数据库结构给后续写升级脚本用。如果你拿到的.db文件是全新版本的工具创建的老组件引擎理论上也能读大部分但遇到新字段类型、新索引特性时不一定认识。遇到这种情况最稳妥的做法是升级驱动而不是用 2010 年的库硬撑。5. 老库能不能继续用兼容性边界和替换建议我在前面说了挺多sqlite-netFx40-2010的好处但我也得说实话它不是一个应该永远无脑使用的库。你需要清楚它的边界。5.1 .NET 4.8 和 Windows 7 下的实际表现实测下来这个老组件在 .NET Framework 4.8 上运行是没问题的因为对 CLR 来说托管 DLL 的版本兼容性做得很好并不会因为你用的是 2010 年编译的程序集就拒绝加载。Windows 7 这类老系统更是它的主战场很多工业上位机项目到现在都还在用。但要注意两点老版组件带的是旧版 SQLite 引擎如果数据库文件里用了新版 SQLite 才支持的语法或特性运行时可能报语法错误如果你在 .NET Framework 4.8 上直接引用这个 DLL而不做任何架构处理仍然会遇到文中所说的位数问题。所以我给团队定的原则是旧的存量项目如果稳定运行不要为了“升级”而升级但新建项目尤其是要长期维护的项目优先评估新一代库。5.2 替代方案里我推荐先试官方的 System.Data.SQLite.Core如果你不想继续用 2010 年的 rar而是在 NuGet 上装包System.Data.SQLite.Core是目前比较大众的选择。它会自动管理 x86/x64 的 Interop DLL你不用手工复制目录安全性也更高。另外一个选择是Microsoft.Data.Sqlite它更轻也避开了旧组件里那套 LINQ 扩展的历史包袱但 API 和System.Data.SQLite有细微差异改造时要注意。我个人经验是这样的如果只是顶一个老系统的个人小工具我会继续用旧组件因为改动最小如果是团队协作、要长期迭代的产品我一定会把数据库访问层抽出来让底层驱动可以被替换。这样以后不管是换Microsoft.Data.Sqlite还是换新版本System.Data.SQLite业务代码都不需要大改。5.3 最后一个建议把 x86/x64 目录当成发布包的一部分不管你最后选哪个库记住一个部署原则把x86、x64目录当成程序本身的组成部分而不是“可选的额外文件”。很多项目用安装包部署打包时只勾了 exe 和 DLL漏掉了子目录里的原生文件结果到了客户现场就翻车。我在公司内部封装了一个发布脚本每次构建完自动把 x86/x64 目录拷到输出目录并在启动日志里打印进程位数和当前加载的 SQLite 引擎版本。这样出了问题看到一个日志文件就能判断是部署问题还是代码问题不用再临时找工具远程看。SQLite 看似是个不起眼的嵌入式数据库但真要把它和 .NET 程序集成好架构和部署细节一点都不能省。你把进程架构、文件目录、引擎位数这三件事理顺了它就能安安稳稳跑很多年。本文还有配套的精品资源点击获取