资讯动态

SQL Server 转 SQLite 实战:SqlConverter 源码拆解与避坑指南

发布时间:2026/10/9 15:05:14 来源:尧图企业网站定制
简介这是一款面向 .NET 开发者的数据库迁移工具用于将 SQL Server 数据库的结构与数据完整转换为 SQLite 数据库解决跨数据库平台迁移、本地轻量化部署等场景下的转换难题。工具使用 C# 开发原作者为以色列开发者 Liron Levi经重新打包、翻译并编译后可直接运行源码完整可自行编译修改。压缩包共 87 个文件约 9.66MB包含 26 个 cs 源码文件、16 个 dll 依赖库、12 个 xml 配置、3 个 exe 可执行程序及 csproj、sln 工程文件等覆盖表结构、视图、触发器、外键、索引等 Schema 解析与转换模块目录结构清晰便于二次开发与调试。目前已有 883 人学习下载。读者可获得一套可直接使用的转换工具与完整源码理解 SQL Server 到 SQLite 的字段映射、Schema 解析与数据迁移思路适合需要数据库迁移或研究跨库转换实现的中高级开发者参考。1. 从 SQL Server 到 SQLite 的迁移现场为什么我最后留了一份 SqlConverter 源码上周帮朋友处理一个老项目的收尾工作场景很典型一套跑了七八年的 SQL Server 2008 R2 业务库现在要缩成一个单机版工具发给客户客户机器上不可能装 SQL Server只能走 SQLite。第一反应是找现成工具试了几个 GUI 导出方案要么卡在datetime字段上要么把IDENTITY自增列直接搬过去变成普通列主键约束全丢。折腾到半夜翻出一份 SqlConverter 的源码包——SQL Server 转 SQLiteDB带完整源码可编译。这东西不是那种一键傻瓜工具它把转换逻辑摊开给你看字段类型映射、建表语句生成、数据搬运全在代码里。适合两类人一类是手头有 SQL Server 老库要往 SQLite 挪的开发者另一类是懒得信任黑盒工具、想自己改映射规则的人。下面把我拆这套源码的过程和踩过的坑完整写一遍能编译、能跑通、能改。2. SqlConverter 的转换链路拆解从连接串到建表语句的完整路径2.1 先搞清楚它到底转了什么SqlConverter 的核心工作不是「复制数据」这么简单。SQL Server 和 SQLite 是两套完全不同的类型系统和约束体系直接搬数据必然翻车。这套源码做的事情分三层第一层读 SQL Server 的系统表拿到表结构、列定义、主键、默认值、索引信息第二层做类型映射和 DDL 重写把 T-SQL 的建表语法翻译成 SQLite 能认的CREATE TABLE第三层才是逐表逐行搬数据用参数化插入写进 SQLite 文件。我拆开源码后画了一下调用链大致是MainForm接收连接串 →SchemaReader遍历INFORMATION_SCHEMA.COLUMNS和sys.indexes→TypeMapper做类型转换 →DdlBuilder拼 SQLite 建表语句 →DataPorter分批读源表、批量写目标库。每一层都可以单独替换这是它比 GUI 工具强的地方——你觉得哪个映射不对改TypeMapper里一个字典就行。2.2 类型映射表哪些能直转哪些必须手动兜底源码里TypeMapper.cs是重点。我把它的映射规则整理成表顺便标注了我实际跑下来觉得需要额外注意的地方SQL Server 类型映射到 SQLite注意事项int/bigintINTEGER直转无精度损失bitINTEGER存 0/1SQLite 没有布尔类型decimal(p,s)NUMERICSQLite 的 NUMERIC 是动态类型精度靠应用层保证float/realREAL直转varchar(n)/nvarchar(n)TEXTSQLite 不限制长度n 被忽略datetimeTEXT存 ISO8601 字符串这是最容易出问题的地方uniqueidentifierTEXT存 GUID 字符串注意大小写varbinary(max)BLOB直转但大字段要分批moneyNUMERIC建议在应用层做定点数处理datetime那一行我单独说。SQL Server 的datetime精度是 3.33 毫秒SQLite 没有原生日期类型源码里默认转成yyyy-MM-dd HH:mm:ss.fff格式的 TEXT。如果你源库里存了1753-01-01这种最小值或者9999-12-31这种边界值转换脚本里得加判断否则写进去读出来会变成NULL。我一般会在TypeMapper里加一个DateTimeFormatter分支把边界值统一转成1900-01-01或者直接抛异常记录日志。2.3 编译环境准备别用最新版 Visual Studio 硬开源码包是 C# WinForms 项目.sln文件对应的框架版本是 .NET Framework 4.0。我用 VS 2022 打开时提示「目标框架不受支持」需要手动装 .NET Framework 4.0 的 Targeting Pack。如果你机器上只有 4.5 以上可以在项目属性里把目标框架改成 4.5.2 或 4.6.1代码本身没有用到 4.0 特有的 API改完直接能编译。依赖项只有两个System.Data.SqlClient连 SQL Server和System.Data.SQLite写 SQLite 文件。后者需要从 NuGet 装System.Data.SQLite.Core注意版本选 x86 还是 x64 要跟你的编译目标平台一致。我第一遍编译报BadImageFormatException就是因为项目设了 AnyCPU但 SQLite 的 native DLL 只放了 x64运行时加载失败。解决办法是在「生成」里把目标平台固定成 x64或者把 x86 和 x64 的SQLite.Interop.dll都放进输出目录。# 如果你用命令行编译先还原 NuGet 包 nuget restore SqlConverter.sln # 指定平台编译避免 AnyCPU 导致的 native DLL 加载失败 msbuild SqlConverter.sln /p:ConfigurationRelease /p:Platformx64编译产物在bin\x64\Release下主程序是SqlConverter.exe同目录必须有System.Data.SQLite.dll和x64\SQLite.Interop.dll。少一个文件就是启动即崩事件查看器里能看到FileNotFoundException。3. 跑通第一次转换连接串、批量提交与日志排查3.1 连接串怎么写才不翻车源码里连接 SQL Server 用的是SqlConnectionStringBuilder界面上让你填服务器、数据库、用户名、密码。我建议直接手写完整连接串贴进去比在界面上分字段填更可控。常见写法// 源码里 SqlServerConnector.cs 的构造逻辑我改成了直接传完整连接串 var builder new SqlConnectionStringBuilder { DataSource 192.168.1.100,1433, // 服务器地址和端口 InitialCatalog OldBizDB, // 源数据库名 UserID sa, Password your_password, ConnectTimeout 30, // 超时设长一点老库响应慢 TrustServerCertificate true // 自签证书环境必须加否则握手失败 };TrustServerCertificate这个参数在 SQL Server 2008 R2 上经常被忽略但如果你连的是启用了强制加密的实例不加就是A connection was successfully established with the server, but then an error occurred during the login process。我踩过一次排查了两小时才定位到证书。SQLite 那边的输出路径也要注意源码默认用Data Sourceoutput.db;Version3;如果目标文件已存在它会直接追加写入不会覆盖。想每次干净转换要么手动删旧文件要么在代码里加File.Delete判断。3.2 批量提交的参数怎么调DataPorter.cs里有个BatchSize常量默认 1000。意思是每读 1000 行源数据就往 SQLite 里提交一次事务。这个值直接影响转换速度和内存占用设太小比如 100事务提交太频繁SQLite 的 WAL 日志疯狂刷盘速度反而慢设太大比如 50000内存里攒的数据集太大老机器上容易 OOM我实测 2000 到 5000 之间比较稳具体看单行数据宽度。改法很简单在DataPorter构造函数里把_batchSize赋值改掉或者在配置文件里加一项读进来。源码里没做配置化我是直接改的常量重新编译。// DataPorter.cs 片段我调整了批量大小和超时 private const int BatchSize 3000; // 每批 3000 行提交一次 private const int CommandTimeout 120; // 单条插入命令超时 120 秒 // 插入时用事务包起来SQLite 没有事务的话每行都是一次磁盘 IO using (var transaction _sqliteConn.BeginTransaction()) { using (var cmd new SQLiteCommand(insertSql, _sqliteConn, transaction)) { // 参数化插入避免拼接 SQL 导致的注入和转义问题 cmd.Parameters.Add(new SQLiteParameter(p0, DbType.String)); // ... 其他参数 cmd.ExecuteNonQuery(); } transaction.Commit(); }参数化插入这点必须强调。源码里原本有一段是字符串拼接INSERT INTO ... VALUES ( value )遇到文本里有单引号直接报错。我把它全部改成了SQLiteParameter顺便解决了datetime格式化的问题——参数传DateTime对象由 SQLite 驱动自己处理格式。3.3 转换日志里该盯哪几行源码的日志窗口会输出每张表的转换进度但默认信息比较粗。我加了几个关键日志点跑的时候重点看Schema loaded: N tables—— 确认源库表数量对不对如果少了可能是权限不够读不到某些 schemaTable [xxx] DDL created—— 建表语句生成成功如果这行后面直接跟异常多半是类型映射没覆盖到Table [xxx] rows: M / Total—— 数据搬运进度如果 M 长时间不动检查是不是锁表了Foreign key constraint skipped—— 源码默认不转外键这行是提示你手动处理。有一次转换卡在rows: 0 / 120000不动查了半天发现源表上有个触发器在往另一张表写数据读操作被阻塞了。解决办法是在连接串里加ApplicationIntentReadOnly或者干脆在业务低峰期跑。4. 避坑与排查SqlConverter 实际转换中的五个血泪教训4.1 现象转换后主键丢了插入重复数据不报错原因SQL Server 的IDENTITY列在 SQLite 里需要显式声明INTEGER PRIMARY KEY AUTOINCREMENT但源码的DdlBuilder只判断了列是不是主键没判断是不是自增。结果建出来的表主键列是INTEGER没有PRIMARY KEY约束。解决在DdlBuilder.cs里加一个判断如果列是IDENTITY且是主键拼成INTEGER PRIMARY KEY AUTOINCREMENT如果只是主键不自增拼成INTEGER PRIMARY KEY。改完重新编译删掉旧的目标库文件再跑一次。4.2 现象datetime字段读出来变成0001-01-01原因SQL Server 的datetime最小值是1753-01-01但 .NET 的DateTime.MinValue是0001-01-01。源码在读取时如果遇到DBNull直接赋了DateTime.MinValue写进 SQLite 就变成这个鬼日期。解决在DataPorter里加空值判断DBNull就写NULL不要转成DateTime.MinValue。另外在TypeMapper里加一个范围检查源值小于1753-01-01的统一替换成1753-01-01并记日志。4.3 现象中文乱码nvarchar转过去变成问号原因SQLite 默认编码是 UTF-8但源码在拼接连接串时没指定Encoding某些环境下System.Data.SQLite会用系统默认编码GBK去写导致中文变问号。解决在 SQLite 连接串里显式加UTF8EncodingTrue;或者在SQLiteConnection打开后执行PRAGMA encoding UTF-8;。我一般两个都加双保险。4.4 现象大表转换到一半报database is locked原因SQLite 的写锁是库级别的转换过程中如果有别的进程比如你开着的 DB Browser for SQLite在读这个文件写操作就会超时。解决转换期间关掉所有可能打开目标库的工具。如果必须在转换后立刻读在连接串里加Journal ModeWAL;WAL 模式下读写可以并发但转换完成后记得执行PRAGMA wal_checkpoint(TRUNCATE);把日志合并回主文件。4.5 现象编译报错The type or namespace name SQLite does not exist原因NuGet 包没还原成功或者引用的System.Data.SQLite版本和项目里packages.config记录的不一致。解决删掉packages文件夹和.vs隐藏目录重新nuget restore。如果还不行手动在.csproj里检查Reference IncludeSystem.Data.SQLite的HintPath是否指向了实际存在的 DLL。我遇到过 NuGet 还原到一半断网DLL 是 0 字节的这种只能删了重来。5. 进阶把 SqlConverter 改成可配置的转换管道5.1 把硬编码映射抽成 JSON 配置源码里类型映射是写死在TypeMapper的switch里的每次改规则都要重新编译。我把它抽成了一个mapping.json程序启动时读进来映射逻辑变成查字典。这样不同项目可以用不同的映射文件不用动代码。{ typeMapping: { int: INTEGER, bigint: INTEGER, bit: INTEGER, decimal: NUMERIC, float: REAL, varchar: TEXT, nvarchar: TEXT, datetime: TEXT, uniqueidentifier: TEXT, varbinary: BLOB }, datetimeFormat: yyyy-MM-dd HH:mm:ss.fff, batchSize: 3000, skipForeignKeys: true }读取配置的代码加在MainForm初始化时用Newtonsoft.Json反序列化。注意batchSize和skipForeignKeys也要从配置里读不要留在代码常量里。这样交付给不同客户时只改 JSON 就行。5.2 加一个转换后的校验步骤转换完不校验等于没转。我在DataPorter结束后加了一段校验逻辑对每张表分别查源库和目标库的COUNT(*)不一致就记日志再随机抽 10 行逐字段比对值。校验代码不复杂但能挡住 90% 的「看起来转完了其实少数据」的问题。// 转换后校验行数比对 抽样字段比对 foreach (var table in tables) { var srcCount GetSqlServerRowCount(table); var dstCount GetSqliteRowCount(table); if (srcCount ! dstCount) { _log.Warn($Row count mismatch on {table}: src{srcCount}, dst{dstCount}); } // 抽样比对逻辑省略核心是逐列取值比较 }校验通过后再把 SQLite 文件发给客户心里踏实。从那以后我每次做数据库迁移不管用什么工具都强制走一遍「转换 → 行数校验 → 抽样比对」三步少一步都不交付。希望这份拆解能帮到你源码包里的注释还算清楚改起来不算费劲。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑