简介:这是一款基于C#开发的Sql Server转SQLite数据库迁移工具,解决开发中需要将SQL Server库结构及数据迁移至SQLite的场景。原作者为以色列开发者Liron Levi,现资源为从CodeProject获取源码后重新打包、翻译并验证可编译的版本,弥补了网上多数同类工具无法直接使用的痛点。压缩包共87个文件,约9.66MB,包含26个C#源码文件、16个DLL依赖库、12个XML配置文件、3个可执行程序以及项目工程文件等,既有完整源代码也有可直接运行的编译产物,便于学习与二次开发。源码中涵盖表结构、索引、触发器、视图、外键等Schema转换核心逻辑,并提供界面选择与失败提示等辅助模块,适合.NET开发者、数据库运维人员以及需要离线转换工具的项目团队。已有880人浏览学习,对需要快速完成SQL Server向SQLite迁移并希望定制转换规则的读者具有实用参考价值。 说实话,一听到"把SQL Server转成SQLite",我第一反应是"这俩能是一回事吗"。一个是要装服务、管账号、吃内存的企业级数据库,一个是只有一个文件的嵌入式数据库,怎么看都不像能直接对话的样子。但实际操作过之后你会发现,这个需求不仅真实存在,而且比想象中频繁得多——用SqlConverter这类带源码的转换工具,把SQL Server的数据搬到SQLite里跑,在很多场景下就是最优解。
这个项目标题里最值钱的不是"转换"这两个字,而是"有源码可编译"这六个字。市面上的数据库迁移工具不少,要么是闭源的黑盒,要么是只能跑固定流程的现成程序,一旦你的表结构复杂一点、数据量大一点、或者SQL Server版本老一点,立马吃瘪。而一个能自己编译的源码项目,意味着你可以看清楚它怎么读源库、怎么建目标表、怎么批量写数据,出了问题能自己改,遇到特殊类型能自己加映射,这才是真正能落地的工具。
接下来我把这个工具从设计思路到实际使用完整拆一遍,包括源码里最核心的类型映射逻辑、编译部署时容易卡的环节,以及我拿真实业务库跑迁移时踩过的坑。这篇东西面向的是正在纠结"要不要转、怎么转、转完会不会出问题"的开发者,尤其是被SQL Server的部署和维护搞得头疼、想在嵌入式设备或本地环境跑SQLite的人。
1. 为什么有人要把SQL Server"降级"成SQLite:场景与边界
先说动机。SQL Server当然是个好东西,事务、权限、高可用、备份恢复全都成熟得不能再成熟。但你如果只是想把一份数据带到某个没有网络的环境里跑,或者想给一个单机版桌面软件配个本地存储,这时候SQL Server就变得很尴尬——光是装一个实例就得几分钟,还要考虑授权、内存占用、防火墙、Windows服务,整套下来比业务本身还重。
SQLite就完全不一样了。它就是一个文件,十几MB甚至几MB的数据库照样跑得很欢,不需要任何后台服务,读多写少的场景下性能一点都不差。所以最常见的迁移需求往往是这三种:一是把生产环境的SQL Server定期同步出一份子集,放到离线分析平台或演示环境里;二是把一个依赖SQL Server的老系统改造为单机部署,直接换掉数据库后端;三是嵌入式设备、小程序、桌面端工具需要一份轻量数据文件,数据源却是历史遗留的SQL Server。
这三种场景下,SqlConverter这类工具的价值就很明确了:它负责把SQL Server里的表结构、数据、索引尽量完整地"翻译"成SQLite能认的样子。但这里有个必须提前说清楚的边界——任何SQL Server到SQLite的转换都不可能是100%等价的,存储过程、触发器、视图这类数据库对象基本都要手动重建或直接放弃,因为SQLite根本没这套东西,或者语法差异太大。你在评估迁移方案时得先接受这个现实,才能往下走。
2. 转换工具的三种路线设计:直连、桥接、中间文件怎么选
看源码之前,先理解一个转换器通常采用哪种数据流架构。我翻过不少同类项目,也自己改过几版,基本都是三种路线:
- 直连模式:程序同时连接SQL Server和SQLite,从源库逐表读取,一边读一边往目标库写。优点是流程短、实时性强,适合一次性的全量迁移,SqlConverter最典型的主路径就是这个。
- 桥接/导出模式:先把SQL Server导出成一批数据文件(CSV、JSON、或者自定义格式),再由另一个环节导入SQLite。好处是解耦,源库可以提前离线,缺点是多了一道工序,数据的一致性校验也更麻烦。
- 内存桥模式:用一个中间数据结构(最常见的做法是DataTable,或者更轻的IDataReader流)把查询结果缓存起来,再批量写入SQLite。本质上也是直连,但重点在于"流式读取",而不是把所有数据一次性压进内存。
绝大多数“有源码可编译”的转换工具,会选择第一种加第三种的组合——即用直连方式打开源库,用IDataReader逐行从SQL Server读取数据,同时借助事务和参数化插入批量写入SQLite,避免一次性把几千万行全塞进内存导致OOM。如果你想改造成增量同步,也通常是在这条主链路上加一个"只读取变更时间晚于上次导入起始点"的过滤条件。
这里的设计取舍是值得多看两眼的:为什么不用DataTable整表Load?因为一列一列、一行一行地复制虽然慢,但内存占用是恒定且可控的。SQL Server一张表几百万行很正常,一旦走DataTable,你的转换进程瞬间吃几个GB内存不在话下,这在开发机上都可能卡,更别说放到Windows Server或Linux容器里跑了。所以好的转换器源码里,核心读取函数一定是用ExecuteReader流式拿数据的,而不是Fill一个DataTable。
3. 源码里最值钱的不是功能,是那套类型映射规则
如果你把SqlConverter的源码拿下来,第一件事不是急着编译,而是先找到那张"类型映射表"。这是整个转换器的灵魂,也是你会不会在后面被坑死的分水岭。
SQL Server和SQLite的类型体系完全是两套思路。SQL Server是强类型SQL引擎,每个字段有非常明确的数据类型、长度、精度、是否允许NULL;SQLite则采用的是动态类型,一个列理论上可以塞任意类型的数据,但转换器为了兼容性和可读性,一般会为每个列显式声明一个SQLite类型。那么映射规则就成了关键中的关键。
我建议你重点检查这些类型的处理:
| SQL Server类型 | 常规映射目标 | 说明与风险点 |
|---|---|---|
INT/BIGINT | INTEGER | 这两个在SQLite里都能对应,但如果你的SQL Server列是BIGINT而表里已经有超过32位范围的值,必须映射成INTEGER,否则插入会产生溢出错误。 |
NVARCHAR/VARCHAR | TEXT | SQLite没有长度限制的概念,所以NVARCHAR(50)直接映射成TEXT最简单,但要注意排序规则和Unicode细节。 |
DATETIME/DATETIME2 | TEXT或NUMERIC | 这是最容易出问题的类型。SQLite没有原生DATETIME,一般存成TEXT并用ISO8601格式(2025-01-01 12:30:00.000),或者存成Unix时间戳的NUMERIC。转换器要是映射错了,你后续做时间范围查询、排序、格式化时全都会乱。 |
DECIMAL(p,s)/NUMERIC(p,s) | NUMERIC | SQLite的NUMERIC是动态的,一般来说能放下绝大部分DECIMAL值,但精度超过15-16位的极值可能产生舍入误差,转换完成后一定要做抽样比对。 |
BIT | INTEGER | 这是经典坑:SQL Server的BIT只有0和1(以及NULL),但SQLite没有布尔类型,映射成INTEGER最稳,读取时0/1自动对应false/true。 |
UNIQUEIDENTIFIER | TEXT | GUID在SQL Server是16字节二进制,转换前要决定是存成GUID.ToString()还是原始字节,前者可读性好、后续好排查,后者空间更省。 |
MONEY/SMALLMONEY | NUMERIC | 需要改成是存decimal还是float,我强烈建议统一转成decimal再写,因为float会产生钞票上面的精度漂移问题。 |
VARBINARY(MAX) | BLOB | 这个没得说,二进制只能进BLOB,但要注意读取时用流式读取,别一次性把几十MB的二进制全加载到内存里。 |
XML | TEXT | SQL Server的XML本质就是字符串,直接ToString()写成TEXT,SQLite不提供XML类型支持。 |
GEOGRAPHY/GEOMETRY | 不支持 | 这类空间数据类型SQLite默认不支持,转换器一般会跳过或报错,你需要自己用WKB/WKT文本形式导出后再手动建表。 |
这张表看起来简单,但绝大多数转换工具的差异就体现在这些细节上。有些开源项目懒得区分DATETIME2和DATETIME,统一按字符串处理,短期没问题,一旦你有大量时间运算就很痛苦。你拿到源码后,第一步应该是按上面这张表检查工具的映射规则,把不满足自己业务的地方直接改掉,再编译。
另外一个最容易忽略的细节是自增主键的处理。SQL Server用IDENTITY(1,1),SQLite用AUTOINCREMENT,源码里必须识别出源表的主键列以及它是否是自增列,然后在建表语句里生成对应的INTEGER PRIMARY KEY AUTOINCREMENT。如果这一步写错,表能建起来,但插入时主键冲突、自增序列混乱是必然的。
4. 拿到源码后的编译与部署要点
标题说"有源码可编译",那么拿到手之后怎么把它变成能跑的程序,这是很多非专门做.NET开发的人容易卡住的地方。SqlConverter这类工具绝大多数是用C#写的,基于.NET Framework或.NET 6/8,开发环境一般用Visual Studio,或者用命令行dotnet build也可以。
编译前你需要确认三件事:
- 目标框架:老一点的项目可能是.NET Framework 4.7.2,新一些的可能已经迁移到.NET 6/8。如果是前者,Windows上装个对应版本的.NET Framework SDK就行,Visual Studio 2019/2022都能编;如果是后者,建议直接装.NET SDK,然后用
dotnet build -c Release一行命令出二进制。 - NuGet依赖:项目里大概率引用了
Microsoft.Data.SqlClient(或老的System.Data.SqlClient)和Microsoft.Data.Sqlite(或System.Data.SQLite),编译时会自动还原,但如果你的网络环境没开NuGet源,需要提前配好镜像源。 - SQL Server访问权限:转换器需要能连上你的SQL Server实例,通常用账号密码登录或者Windows身份验证,编译本身不涉及,但运行时的连接字符串必须提前确认好。
我自己的经验是,先在命令行里跑通,再考虑GUI封装。很多这类项目是控制台程序,参数无非就是/source:"Server=...;Database=...;User Id=...;Password=...;"和/target:"D:\output.db"。你拿到源码后别急着改界面,先直接用命令行传连接串跑一遍,确认工具本身可用,再根据业务需求去改代码加功能。这样即使后续报错,你也能最快定位到是连接问题、权限问题、还是类型映射问题。
如果你是在Linux或macOS上编译运行,也不是不行,只要用的不是Windows-only的SQL Server验证模式。Microsoft.Data.SqlClient支持跨平台连接SQL Server,SQLite更不用说,本身就是跨平台库。所以这份源码理论上可以在Linux容器里跑定时任务,定期把SQL Server的某几张表同步成SQLite文件,再放到对象存储或分发给下游——这个组合非常适合做数据分发。
5. 一次真实业务库的迁移实操:从连接串到数据校验
理论讲多了容易飘,我拿一次真实场景来走一遍完整流程。客户那边有一个老旧的SQL Server 2008 R2实例,里面有大概30张表,最大的表1200万行,带一堆索引,总数据量大约20GB。目标是把其中指定的15张业务表转换成一个SQLite文件,丢到一台加固的离线电脑上做只读查询。
第一步是确认源库连接。这里有个容易踩的坑:SQL Server 2008 R2太老,新版Microsoft.Data.SqlClient默认的加密行为可能导致连接失败,因为老版本SQL Server不支持TLS 1.2。解决办法是在连接字符串里显式加上Encrypt=False或者TrustServerCertificate=True。你要是连2008 R2连不上,打开日志一看是加密相关的错误,基本就是这个原因。
第二步是设计目标表。转换器会按映射规则自动建表,但我会先手动查看几张核心表的建表SQL,确认主键、外键、索引是不是按预期创建。有一张订单表,源库主键是INT IDENTITY,SQLite这边生成的表如果有问题,我会直接在源码里调整主键生成规则,而不是等转换完再手工修。
第三步才是跑转换。我的命令大概是这样的:
SqlConverter.exe \ --source "Server=192.168.1.10;Database=OrderDB;User Id=sa;Password=***;Encrypt=False;TrustServerCertificate=True" \ --target "C:\export\order.db" \ --tables "Orders,Customers,Products,OrderDetails" \ --batch-size 5000启动之后观察输出日志。一个设计良好的转换器会逐表打印进度:正在读取Orders表结构、正在写入数据、当前行数、耗时。1200万行的表,走流式读取加事务分批提交,大概几分钟到十几分钟不等,取决于源库查询速度和磁盘IO。这个过程中,进程内存保持稳定在一个区间内,这就是流式读取的效果。
第四步是验证。转换完成不是终点,千万不能看日志输出"完成"就收工。我通常会在SQLite这边执行几条和源库对得上的聚合查询,比如总数、最大值、最小值、分组计数,逐表比对。再抽查几条明细数据,特别注意时间字段的格式是否一致、金额字段的精度有没有丢、字符串有没有乱码。这个环节能救回不少隐藏的类型映射错误。
最后一步是把生成的DB文件备份一份,然后用SQLite自带的PRAGMA integrity_check跑一下完整性校验。这一步很多人会忽略,但它能快速发现页损坏或写入中断的问题。上面的流程走完,这个SQLite文件才可以放心交付。
6. 我在迁移过程中踩过的坑与源码可扩展方向
按惯例,最后分享几个实操中遇到的坑,这些都是看文档看不出来的东西。
第一个坑是大批量事务回滚导致的锁等待。SQLite在写一个比较大的事务时,如果磁盘IO慢,整个库文件会被锁定,其他进程的读操作会一直等。转换器默认每5000行提交一次事务,这本身没问题,但如果你同时在另一个进程里用SQLite打开同一个文件,就会遇到"database is locked"。解决方案是转换期间不要开任何外部连接,或者把PRAGMA busy_timeout调大一点,再或者干脆把目标文件放在本地SSD上而不是共享目录里。
第二个坑是外键约束的顺序问题。如果转换器按SQL Server的sys.objects顺序建表,很可能先建了含外键的子表,再建父表,而SQLite默认允许建表时引用一个不存在的父表吗?如果开启了外键约束PRAGMA foreign_keys = ON,而且建表顺序不对,就会报错。一个稳妥的源码改动是按依赖拓扑排序建表,或者在建表阶段先不启用外键,等数据全部写完后再手动打开外键约束。
第三个坑是字符串里的非法字符和编码。SQL Server的NVARCHAR里可能藏着很怪的控制字符、代理对字符、甚至是未配对的代理项,直接写入SQLite的TEXT列后,某些工具读取时可能乱码甚至崩。源码里最好在写入前做一次字符清洗,把未配对代理项替换成U+FFFD,这是我在实际项目中踩过的很诡异的问题,银行客户数据里居然真能碰到这种字符。
第四个坑是索引的碎片化与体积膨胀。SQLite没有SQL Server那种在线重建索引的机制,转换过程中不断写入,索引文件会膨胀。转换完成后跑一下VACUUM能显著缩小文件体积。我在客户机器上实测过一个1.2GB的DB文件,VACUUM后变成800MB,差别非常明显。
关于后续扩展,如果你打算长期维护这个工具,我建议优先改三个方向。第一是支持增量同步,在源表上加一个LastModified时间戳过滤条件,就能把全量迁移升级成定期增量同步;第二是支持更多目标数据库,源码里如果映射层写得很干净,加一个PostgreSQL或MySQL目标也就是加一组映射规则的事;第三是加一个数据校验模块,转换完自动执行核对SQL,把结果输出成一份报告,这个对交付给客户特别有用。
源码在手,一切皆可改。与其到处找免费的转换工具,不如花一个下午把映射逻辑吃透,改成最适合自己业务的专有工具。等你在真实项目里跑通了第一版,再回头看,你会觉得把SQL Server的数据搬进SQLite这件事,远比想象中干净利落。
本文还有配套的精品资源,点击获取