简介:这是一份基于C#开发的数据库迁移工具,可将Sql Server数据库完整转换为SQLite格式,不仅迁移表结构与数据,还覆盖索引、触发器、视图以及外键关系的处理。项目源自CodeProject作者Liron Levi的公开源码,经重新打包、汉化与编译验证,解决了网上多数同类工具不可用的问题,适合需要快速迁移数据库或研究C#数据库转换实现的中高级开发者。压缩包共包含87个文件,约9.66MB,主要涵盖C#源码文件、依赖动态库、XML配置文档、调试符号、可直接运行的程序,并附带解决方案与项目文件,便于在Visual Studio中打开并重新编译。工具内置表选择界面,可单独挑选需要迁移的表,转换后即生成SQLite数据库文件,便于本地测试或集成到其他应用。目前已有884人学习下载,对于希望掌握SQL Server到SQLite迁移思路、研读表架构与触发器转换逻辑,或直接获取可用工具的人来说,具备较强的实用与参考价值。
1. 把 SQL Server 数据搬进 SQLite:源码头文件与可编译工具的边界在哪
做数据迁移时最头疼的不是写转换代码,而是你手上的库环境跟目标环境根本对不上。别人给你一个 SQL Server 的备份文件,你本机没装完整数据库服务,或者只想在离线分析场景里用一个轻量级的存库文件,这时候把 SQL Server 结构连同数据完整抬进 SQLite 就是刚需。这个工具恰恰不是在线的数据同步方案,而是一个可编译的源码头文件转换器:它读取 SQL Server 侧的库结构,导出库表定义、索引和数据,最终生成一个独立的 sqlite 文件。对需要把某套业务系统的历史数据做归档、做离线分析、或者在本地搭一套只读查询环境的人来说,这个工具能省掉大量手写建表语句的时间。源码开放且可自行编译,意味着你可以改映射规则、改导出逻辑,而不是拿着一个黑匣子去迁数据。
2. 编译这条命令之前:环境配置与 C++ 源码构建的三步走
拿到源码包之后,大多数人第一反应是找 exe。这个项目不是开箱即用的绿色软件,而是需要你自己在编译环境里把它构建出来。所以先别急着跑数据,把构建链路打通再说。
2.1 为什么是 C++ 而不是 C# 或 Python:编译型工具在迁移中的定位
跨库迁移工具的选择逻辑不复杂:如果你只需要转换一次两次,Python 脚本加第三方库最舒服,改起来快,跑完就扔。但这个项目选择做成 C++ 可编译形态,原因有两个,一个是为了能在目标机器上独立运行,二是转换过程本身涉及源数据库连接、Schema 读取、数据批量抓取、本地文件写入,这几步对内存和 I/O 的稳定性要求不低。
C++ 编译出来的程序在运行时不需要额外的运行时框架或者安装一大套依赖库。你在客户的机器上执行一个转换工具,总不希望先安装 200MB 的运行时再干活。源码编译出的可执行文件配合对应的数据库访问组件,能让整个转换动作保持整洁。我一般会把这个工具定位成“数据搬运的一次性设备”,要的就是它在 Windows 环境下安静地把活干完。
2.2 共用运行时、编译器路径与 x86 还是 x64 的取舍
编译之前先检查三样东西:数据库访问组件、C++ 编译器和目标平台位数。这个工具依赖数据库访问组件去连接 SQL Server 实例,而不是自己实现数据库协议,所以源码包里通常带着对应的头文件和导入库。
我用 Visual Studio 构建这个项目时报错过一次,仔细看是平台选成了 x64,但数据库访问组件装的是 x86 版本。后来把解决方案平台改成 x86 或者把组件换成 64 位版本就通过了。这里有一个通用的编译前检查项,直接照着做就能少走弯路。
# 检查编译器是否可用,64 位系统上通常会显示 v143 或类似版本号 # 这个命令在 Visual Studio 开发者命令行里执行 cl /? 2>nul | findstr /i "Version" || echo "未检测到 MSVC 编译器"逻辑说明:这行命令是在确认 C++ 编译器能不能直接用。工具项目不是一个自带完整编译链的项目,它需要借助 Visual Studio 或者 Build Tools 提供的cl.exe来构建。如果你在命令行里敲cl /?没有任何输出,说明当前环境缺少 C++ 构建工具,得先去安装包含 MSVC 编译器的工作负载。
参数说明:/?是让编译器打印帮助信息,findstr /i "Version"是粗过滤一下输出内容。实际使用中你不需要这条命令做太多事,它只是帮你确认环境是否就绪。
接下来打开解决方案文件,先别急着点生成。右键解决方案,在“配置管理器”里把活动解决方案平台设为 x64,然后单独检查每个项目的平台是否也是 x64。如果机器内存小于 4GB,建议直接选 x86,因为转换大库时内存占用会迅速攀升。
# 生成项目并输出到指定目录,Release 模式避免调试信息拖慢执行速度 msbuild SqlConverter.sln /p:Configuration=Release /p:Platform=x64 /m:4逻辑说明:msbuild是 Visual Studio 的命令行构建工具,/m:4表示用 4 个进程并行编译,能加快构建速度。/p:Configuration=Release表示编译发布版本,/p:Platform=x64指定 64 位输出。编译完成后,在解决方案目录下的x64\Release文件夹中能找到生成的 exe。
参数说明:这里Platform=x64必须和前面配置管理器里的选择保持一致。如果你在配置管理器里选的是 x86,命令行里就要写Platform=x86,否则会报平台不匹配的错误。
2.3 编译输出与 bin 目录文件说明
编译成功后,你会在输出目录里看到几个文件,其中最重要的是主程序文件。我拆过的这个源码包里,编译输出的文件结构大概是这样的:
| 文件名 | 作用 | 缺失后果 |
|---|---|---|
| SqlConverter.exe | 主程序入口 | 无法启动工具 |
| 若干 DLL | 数据库访问组件和运行时依赖 | 转换时报“找不到组件”错误 |
| 说明文件 | 使用方法和依赖说明 | 不致命,但会影响参数理解 |
注意:编译输出的 DLL 不能随手删除。转换工具在连接 SQL Server 时需要动态加载这些组件,缺一个都会导致程序在启动阶段直接退出。我习惯把整个输出目录复制到一台干净的机器上测试,如果那台机器能跑通转换,说明依赖齐全。
3. 拆开源码看转换逻辑:方案选择、Schema 提取与类型映射
编译通过只是第一步,真正决定这个工具好不好用的是它内部的转换逻辑。源码如果把转换规则写死了,那遇到某些特殊表结构时你会非常被动。好在源码开放的窗口就在这里,你可以看清它是怎么读取库结构、怎么映射数据类型、怎么生成 sqlite 文件的。
3.1 工具源码结构:入口函数、数据库访问与导出器组织
源码的组织方式直接决定了你改进它时的难度。这个项目的代码模块一般分成三块,第一块是连接管理,负责跟 SQL Server 建立连接并抓取元数据;第二块是结构映射,负责把库表定义翻译成 SQLite 能接受的建表语句;第三块是数据导出,负责把源表数据一批一批写进目标文件。
// 伪代码示意:主流程的三个步骤 DatabaseSource source; source.Connect(connectionString); if (!source.IsConnected()) return -1; SchemaExtractor extractor; auto tables = extractor.GetTables(source); SqliteExporter exporter; exporter.SetOutputPath(outputPath); exporter.Export(tables);逻辑说明:这是我按源码模块的习惯整理出的主流程。Connect负责建立数据库连接,GetTables负责读取库中的表集合,Export则是真正把数据和结构写入 sqlite 文件。实际源码里每个类都更庞大,但流程骨架不会有太大偏差,看懂这条主线,你就知道在哪个环节可以插自己的逻辑。
参数说明:connectionString在真实使用时会包含服务器地址、数据库名称和登录凭据。某些版本也可能把连接参数放在外部配置文件里,使用前需要确认。
3.2 数据库结构提取里的隐式约束:为什么直接生成建表语句不总是可行
很多人第一次写 SQL Server 转 SQLite 的脚本时,思路很直接:从系统表里读字段,拼成 CREATE TABLE 语句,一个表一个表地生成。这个思路在小库上没问题,但库一复杂就会遇到隐式约束。
SQL Server 的表结构里存在外键关系,直接导出时如果不维护表之间的依赖顺序,先删了父表再建子表,外键就建不成功。这个工具的源码里通常会在导出前先分析表之间的引用关系,把建表顺序排好,避免“父表未建,子表先建”的尴尬。
另一个隐式约束是标识列。SQL Server 的 IDENTITY 字段在 SQLite 里一般映射为 INTEGER PRIMARY KEY AUTOINCREMENT,但如果源表里这个字段不是主键,映射规则就要重新定义。源码里一般会有专门的类型判断函数来处理这些边界情况。
// 伪代码:判断列类型并返回 SQLite 对应的存储类型 std::string MapColumnType(const ColumnSchema& col) { if (col.type == "int" || col.type == "bigint") return col.isPrimaryKey ? "INTEGER PRIMARY KEY" : "INTEGER"; if (col.type == "nvarchar" || col.type == "varchar") return "TEXT"; if (col.type == "datetime") return "TEXT"; return "TEXT"; }逻辑说明:这是一个简化的类型映射函数,真实源码里会比这个更复杂,但核心思路一致:SQL Server 类型对应到 SQLite 的一级存储类型。值得注意的是,SQLite 的类型亲和性比较宽松,所以把 NVARCHAR 映射成 TEXT 不会有问题,DATETIME 也映射成 TEXT 可以在转换后保留完整的时间字符串格式。
参数说明:ColumnSchema里至少包含字段名、原始类型、是否主键等信息。你在改映射规则时,只需要修改这个函数里的条件分支,不用动其他模块。
3.3 类型映射对照表:做转换不单单是“改一下后缀”
跨库迁移最不能偷懒的就是类型映射。我见过不少迁移翻车现场,原因都是类型映射想当然。
| SQL Server 类型 | SQLite 目标类型 | 注意事项 |
|---|---|---|
| int / bigint / smallint | INTEGER | 长度无需指定 |
| decimal / numeric / money | NUMERIC | 保留精度,SQLite 会存为数值类型 |
| nvarchar / varchar / char | TEXT | 长度限制在 SQLite 中可忽略 |
| datetime / datetime2 / date | TEXT | 保留原始字符串,避免时间戳转换误差 |
| bit | INTEGER | 0/1 存储,SQLite 没有独立的布尔类型 |
| varbinary / image | BLOB | 二进制字段必须映射为 BLOB 否则数据损坏 |
| uniqueidentifier | TEXT | 保留 GUID 字符串,无可比性差异 |
这里最坑的是 datetime 类型。SQL Server 的 datetime 精度到毫秒,SQLite 如果不指定类型就会按 NUMERIC 处理,最终存进去的时间值可能出现精度丢失。源码里一般默认把日期时间类型转成 TEXT,这样存进来的是 "2025-06-01 12:30:45.123" 这样的字符串,查询时也不影响排序和比较。
另一个不值得踩的坑是 BIT 类型。如果映射规则把 bit 当成 INTEGER 处理,读出来的 1 和 0 在 SQLite 里表现正常,但如果转成 TEXT,写进去就成了 "True/False" 字符串,后续条件查询WHERE flag = 1就失效了。你看源码时重点检查有没有对 bit 做特判。
4. 把备份文件转成 sqlite 的一次完整操作:入参、清单与结果核对
理论看完了,现在走一遍实际转换流程。这里我模拟一个真实的迁移场景:某开发者拿到一个业务系统的数据库备份,需要把它转成 SQLite 供一个离线分析工具读取。整个操作按“准备、转换、验证”三个阶段推进。
4.1 转换前的清单检查:缺 DLL、缺权限、缺说明文件的三个先决条件
没见过 SQL Server 实例就急着跑转换工具的人不在少数。工具跑起来后连接失败,把错误日志贴出来才发现是服务器实例名写错了。
我整理了一个转换前检查清单,照着打勾能减少大量无谓挣扎:
- 数据库访问组件是否已安装,且位数匹配(x86/x64)
- 能否通过网络访问到 SQL Server 实例,端口是否被防火墙拦截
- 登录账号是否有读取库元数据的权限(至少需要 db_datareader 角色)
- 目标磁盘剩余空间至少为源库大小的 1.5 倍
- 输出路径不能包含中文字符或空格(部分组件对路径敏感)
如果检查不通过,工具可能在启动时报错,也可能在转换进行到一半时中断。前者不可怕,后者才让人崩溃。
4.2 实际操作:入参细节与转换日志解读
工具通常支持两种输入方式,一种是通过命令行参数直接指定数据库连接,另一种是在界面里填连接信息。命令行方式的典型调用长这样:
SqlConverter.exe "Server=localhost;Database=SourceDB;Integrated Security=True;Trusted_Connection=yes" output.sqlite逻辑说明:第一个参数是 SQL Server 连接字符串,第二个参数是输出的 SQLite 文件路径。这个命令用 Windows 身份验证连接本机的数据库实例,适用于开发机。如果你要用账号密码登录,把连接字符串改成Server=192.168.1.10;Database=SourceDB;User ID=sa;Password=xxx即可。
参数说明:Integrated Security=True表示用当前 Windows 账户访问数据库,Trusted_Connection=yes是它的同义写法,二选一就行。User ID和Password是指定 SQL Server 账号认证,注意在命令行里暴露密码有安全风险,建议在测试环境或脚本中设置好权限后再执行。
转换开始后,工具一般会在控制台输出日志,逐步打印每个表的处理状态。正常的日志模式是每个表一行,包含表名、记录数、耗时。如果你的库里有几百张表,看到日志刷刷刷往下滚属于正常现象。
转换过程中最需要注意的是表名和字段名的特殊字符。某些源库的表名里带空格或关键字,工具在生成建表语句时会自动加上双引号规避 SQLite 的语法错误。如果日志里出现table "Order Details"这种带引号的输出,说明正在处理的是含特殊字符的表,不用慌。
4.3 转换输出与 SQLite 侧验证
转换完成后,生成的 sqlite 文件并不会自动通过完整性检查。我习惯在转换完成后立刻用 SQLite 的命令行工具或者任意一个支持 SQLite 的客户端打开文件,跑三条验证语句:
-- 验证表数量是否和源库一致 SELECT count(*) FROM sqlite_master WHERE type='table'; -- 抽查某张核心业务表的记录数 SELECT count(*) FROM orders; -- 验证外键是否生效 PRAGMA foreign_key_list(orders);逻辑说明:第一条语句统计的是 sqlite 系统表中的用户表数量,和源库的表数量做对比能发现漏表问题。第二条语句抽查记录数,是最直接的“数据搬没搬全”验证。第三条语句查看外键定义,确认结构导出没有丢失引用关系。
参数说明:orders是我模拟场景里的业务表名,换成你实际库里的核心表即可。sqlite_master是 SQLite 内部维护的系统表,不需要手动创建。
三条语句执行完,只要数量级对得上,表结构完整,这次转换就算成功收工。如果数量对不上,优先检查源库在转换期间是否有写入操作,因为工具抓取的是某个时间点的数据快照,源库的并发写入会导致最终结果不一致。
5. 避坑记录:一次真实的 SQL Server 转 SQLite 排查经历
转换工具不是银弹,实际迁移过程中会遇到各种意料之外的问题。我在一次给某跨平台系统做数据迁移时,用这个工具连续翻车四次,每次踩坑都有明确的原因和解决路径,挑几条最有代表性的记录在这里。
5.1 表导出一半报错:内存溢出还是组件崩溃
现象:转换进行到某张大表(约 2000 万行)时,程序弹出“内存不足”的错误,进程直接退出。
原因:源码里数据导出是批量读取,但某些版本对 varchar(max) 和 varbinary(max) 字段的处理没有做分段读取,一次性把整列数据加载到内存,导致内存峰值暴涨。我检查输出日志时发现崩溃前最后处理的一张表里有几个超大日志字段,字段里存的是长度超过 10MB 的文本。
解决:把转换拆分执行,先转换不含大字段的表,再单独处理含大字段的表。如果工具支持批量大小参数,可以调小每次抓取的行数,比如从默认的 1000 行改成 200 行,让内存压力分散到多次读取中。
5.2 中文数据变成乱码:连接字符串的字符集没配
现象:转换完成后,用 SQLite 客户端打开文件,所有中文字段显示为乱码,英文字段正常。
原因:SQL Server 与 SQLite 之间的数据传输走的是客户端组件的默认编码。我检查连接字符串时发现没有显式指定字符集,组件默认使用了非 UTF-8 编码,导致中文字符在写入 sqlite 文件时被错误编码。
解决:在连接字符串中追加Character Set=UTF8参数,重新执行转换。转换完成后再次抽查中文数据,确认显示正常。
5.3 转换后外键全部丢失:结构导出顺序问题
现象:sqlite 文件生成成功,表数量也对,但所有外键都不存在,PRAGMA foreign_key_list查不到任何记录。
原因:工具在导出结构时,某些表的外键引用的目标表排在自身后面。SQLite 在创建表时遇到还未存在的目标表会直接忽略外键定义,而不是延迟到后面再补。这个工具的版本没有对表依赖做拓扑排序。
解决:转换完成后,自己手工把外键补上。写一个 SQL 脚本,用ALTER TABLE语句逐个添加缺失的外键约束。更稳妥的方案是修改源码中的导出顺序逻辑,让被引用表先建。若没有改源码的能力,只能在目标文件上做后处理。
5.4 转换时间过长,一晚上都没跑完
现象:一个几十 GB 的大型业务库,跑了六个小时还没结束,日志显示后面的表速度越来越慢。
原因:目标 sqlite 文件在写入时没有关闭自动提交,每插入一批数据就触发一次事务提交,磁盘 I/O 压力巨大。源码里如果使用了批量插入但没有显式开启事务,就会频繁写盘。
解决:转换前先把 SQLite 的同步模式调低,用PRAGMA synchronous=OFF临时关闭同步写盘。同时确认源码在导出数据时外层包了一个大事务,只有全部插入完成才提交一次。如果源码没有自动加事务,转换 SQLite 文件可以在完成后重新打开执行PRAGMA integrity_check来确认文件没有损坏。
5.5 生成的 sqlite 文件体积异常偏大
现象:源库 10GB,转出来的 sqlite 文件有 18GB,几乎翻了一倍。
原因:SQL Server 中很多表的 varchar 字段实际存储时并没有填满定义长度,但转换工具在写入 SQLite 时可能为每个 TEXT 字段预留了空间,或者页大小配置不合理。另一个常见原因是某些表使用 BLOB 存储字符串,占空间比 TEXT 更夸张。
解决:转换完成后执行VACUUM命令压缩文件。这个操作会重建整个数据库文件,回收空闲页和碎片空间,通常能把体积压缩回合理范围。注意执行 VACUUM 前确认磁盘剩余空间足够,因为它需要一份临时文件来存重建后的数据。
6. 把转换器押成批处理:cmd 调用、自动生成说明文档与批量迁移
工具单次运行没问题后,你大概率会遇到“隔三差五就要转一次库”的场景。可能是业务系统定期归档,也可能是你有多个库要迁。这种时候手敲命令已经不现实了,把工具封装成批处理脚本是更贴近一线的做法。
6.1 把参数写进脚本:多库转换的最小可用方案
写一个 cmd 脚本包装转换命令,这样每次只需要改库名和路径,不需要记连接字符串的格式。
@echo off set SERVER=localhost set DB_LIST=UAT_Core 2025_Archive 2025_Quarter3 for %%D in (%DB_LIST%) do ( echo converting %%D... SqlConverter.exe "Server=%SERVER%;Database=%%D;Integrated Security=True" "D:\sqlite_out\%%D.sqlite" ) echo done.逻辑说明:这个脚本的核心是 for 循环遍历库名列表,逐个执行转换。%%D是批处理中的循环变量,每轮循环会把DB_LIST里的一个库名赋给它。set SERVER=localhost是一次性配置,后面替换起来方便。
参数说明:这里用 Windows 身份验证,所以脚本里没有密码。如果目标机器需要 SQL Server 账号认证,把Integrated Security=True替换成User ID=你的账号;Password=你的密码就行,但注意别把带密码的脚本提交到版本库。
6.2 表结构校验脚本:让转换结果自己会说话
转换完成不代表数据可用,我在批处理末尾会加一个自动校验步骤,让脚本自己检查每个输出文件的表数量是否和源库一致。这里用到 SQLite 的命令行工具配合写一个简单的查询。
sqlite3 D:\sqlite_out\UAT_Core.sqlite "SELECT count(*) FROM sqlite_master WHERE type='table';" > table_count.txt copy /Y table_count.txt D:\sqlite_out\counts\UAT_Core_count.txt逻辑说明:第一条命令把输出文件里的用户表数量写入文本文件,第二条命令把这个结果挪到统一的统计目录里。脚本跑完,打开 counts 文件夹,所有库的表数量一目了然,和源库的数量比对后就能快速发现有没有漏表。
参数说明:sqlite3是 SQLite 官方的命令行客户端,如果你机器上没有装,可以用任意编程语言里的 SQLite 模块替代。
6.3 自动生成转换说明文档:给运维留一手反悔药
做数据迁移最怕的就是转完没人说得清这文件是怎么来的。我后来养成一个习惯,每次批量转换执行完,脚本自动往输出目录写一个 README 文件,记录转换时间和源库信息。
echo 数据库转换记录 > 转换说明.txt echo 转换时间: %date% %time% >> 转换说明.txt echo 源库列表: UAT_Core, 2025_Archive, 2025_Quarter3 >> 转换说明.txt echo 目标文件目录: D:\sqlite_out >> 转换说明.txt echo 转换工具: SqlConverter 源码编译版 >> 转换说明.txt逻辑说明:这四行 echo 命令把关键信息追加写入文本文件,等效于给每个输出目录加了一份使用说明。不要觉得这个动作多余,当你一个月后回来看这批文件,或者你的同事接手这个项目时,这份记录就是救命的线索。
参数说明:%date%和%time%是 Windows 系统的环境变量,脚本执行时会自动展开成当前日期时间。如果你希望记录更详细,可以在转换日志里追加每个库的处理耗时,但当前这个版本已经足够。
从那以后,我每次用这个工具转换完数据库,都强制自己走一遍“编译产物完整性检查、转换日志解读、SQLite 侧验证、说明文档生成”这四个环节。这套流程看着机械,但实实在在帮我避开了至少三次“转完了却发现数据对不上”的尴尬。工具是死的,流程是活的,希望这份实操笔记能帮你把跨库迁移这条路走顺,希望帮到你。
本文还有配套的精品资源,点击获取