
简介这是一款基于C#开发的SQL Server到SQLite数据库迁移工具面向.NET开发者、数据库管理员及嵌入式系统工程师解决跨平台数据库轻量化迁移难题——尤其适用于将企业级SQL Server数据快速转换为SQLite格式用于移动端、桌面应用或离线场景。资源包共87个文件含26个核心C#源码文件如SqlServerToSQLite.cs、TableSchema.cs、TriggerBuilder.cs等、16个运行依赖DLL、3个可执行程序exe及配套配置文件config、settings完整覆盖编译、调试与部署所需压缩包大小9.66MB结构清晰便于二次开发与功能定制。已有882人学习下载。用户可直接编译运行获得带图形界面的转换器支持表结构、索引、外键、视图、触发器及全部数据的一键迁移并附有详细日志与失败诊断对话框ViewFailureDialog显著降低迁移门槛与调试成本。1. 为什么把 SQL Server 数据迁到 SQLite 不是“导个 Excel”那么简单一个能编译、可调试、真落地的转换器到底在解决什么你手头有个运行了五年的 SQL Server 数据库现在要打包进一个桌面应用或嵌入式设备——比如给现场巡检人员用的离线 App或者给边缘网关做本地缓存。你试过 SSMS 导出为 CSV 再用 DB Browser 手动建表结果发现datetime2字段全变成乱码、uniqueidentifier被截断成空字符串、computed column直接丢弃、CHECK CONSTRAINT全消失、DEFAULT (getdate())变成NULL……更糟的是上百张表里混着ROWVERSION、hierarchyid、FILESTREAM这些 SQLite 根本不认的类型。这不是数据格式问题是语义鸿沟SQL Server 的 T-SQL 是带状态、带行为的“程序语言”SQLite 的 SQL 是纯声明式的“数据协议”。SqlConverter_Sql Server转为SqliteDB_有源码可编译这个项目核心价值不是“多一个转换按钮”而是提供一条可控、可审计、可定制的语法翻译流水线——它把 SQL Server 的.bak或.mdf经sqlservr.exe提取后或SELECT * FROM sys.tables元数据逐层解构为 AST再按 SQLite 语义重写 DDL DML最后生成.sqlite3文件和配套迁移日志。适合三类人需要离线部署的工业软件工程师、做跨平台桌面应用的 C/Qt 开发者、以及被客户逼着“把老系统数据塞进新 App”的实施工程师。它不承诺 100% 自动化但把“手动改 200 张表脚本”压缩成“调参 编译 验证 3 步”。2. 从源码结构看设计逻辑为什么这个转换器必须自己编译而不是装个 NuGet 包就完事这个项目不是封装好的黑匣子而是一个典型的“元数据库工具链”它不直接连接 SQL Server 实例避免驱动依赖和权限问题而是先通过sqlcmd或bcp导出结构定义.sql和数据.csv或.tsv再用自研解析器处理 T-SQL最后生成 SQLite 兼容脚本。整个流程分三层元数据提取层 → 语法转换层 → 目标生成层。源码目录结构清晰暴露了这种分层/src /Core # AST 解析器、T-SQL 词法分析器、SQLite 语法生成器C# /DataExtractor # bcp 命令封装、schema 查询模板、约束提取逻辑 /Generator # CREATE TABLE 重写规则、INSERT 语句适配、索引/触发器映射 /CLI # 命令行入口、参数解析、进度回调、错误聚合 /test # 针对 datetime2 → TEXT、money → REAL 等关键类型的单元测试提示项目未使用 Entity Framework 或 Dapper 等 ORM 层因为 ORM 会掩盖底层类型差异。例如 EF Core 6 对datetime2默认映射为DateTime但在 SQLite 中实际存为 ISO8601 字符串——这会导致WHERE date 2023-01-01查询失效。本项目选择直面 SQL 文本确保每行CREATE TABLE都经过显式重写。2.1 源码编译前必做的三件事环境、依赖、路径项目基于 .NET 6 SDK 构建非 .NET Framework因此 Windows/macOS/Linux 均可编译但需注意Windows安装 Visual Studio 2022含 .NET 6 SDK 或仅安装 .NET 6 SDKmacOS/Linuxbrew install dotnet-sdk6macOS或apt-get install dotnet-sdk-6.0Ubuntu 22.04关键依赖检查System.Data.SqlClient已弃用项目改用Microsoft.Data.SqlClientv5.1.5支持 SQL Server 2022 连接SQLite 侧使用Microsoft.Data.Sqlitev7.0.5适配 .NET 6验证环境是否就绪dotnet --version # 输出应为 6.0.x dotnet list package --include-transitive | grep -i microsoft.data # 应看到 microsoft.data.sqlclient 和 microsoft.data.sqlite2.2 编译命令与输出结构生成的是 CLI 工具不是 DLL项目根目录下SqlConverter.sln是 Visual Studio 解决方案但生产环境推荐用 CLI 编译避免 IDE 版本差异cd /path/to/SqlConverter_Sql_Server转为SqliteDB_有源码可编译 dotnet publish -c Release -r win-x64 -o ./publish-win --self-contained true # 或 Linux: -r linux-x64 # 或 macOS: -r osx-x64--self-contained true是关键它把 .NET 运行时打包进输出目录生成的SqlConverter.exeWindows或SqlConverterLinux/macOS可直接拷贝到无 .NET 环境的机器上运行。输出目录结构如下./publish-win/ ├── SqlConverter.exe # 主程序含所有依赖 ├── SqlConverter.deps.json # 运行时依赖清单 ├── SqlConverter.runtimeconfig.json # .NET 配置 ├── lib/ # 项目私有库如自定义 AST 解析器 └── migrations/ # 示例转换脚本供调试用注意-r参数指定运行时标识符RID必须与目标机器一致。若在 Windows 上编译却要部署到 Linux ARM64 设备需用-r linux-arm64并确保本地有对应 SDKdotnet sdk install --arch arm64。2.3 核心转换逻辑拆解一张表的“重生”过程以 SQL Server 中这张典型业务表为例CREATE TABLE dbo.Orders ( Id INT IDENTITY(1,1) PRIMARY KEY, OrderNo NVARCHAR(20) NOT NULL, CreatedAt DATETIME2 NOT NULL DEFAULT GETDATE(), Status TINYINT CHECK (Status IN (0,1,2)), RowVersion ROWVERSION );转换器执行时会经历以下步骤元数据提取执行SELECT ... FROM sys.columns c JOIN sys.types t ON c.user_type_id t.user_type_id获取列定义识别DATETIME2、TINYINT、ROWVERSION类型AST 构建将CREATE TABLE语句解析为树节点DEFAULT GETDATE()被标记为FunctionCallNodeCHECK约束为CheckConstraintNode规则匹配与重写DATETIME2→TEXTSQLite 无原生 datetime 类型存 ISO8601 字符串TINYINT→INTEGERSQLite 无位宽概念INTEGER可存 -9223372036854775808 ~ 9223372036854775807DEFAULT GETDATE()→DEFAULT CURRENT_TIMESTAMPSQLite 等效函数CHECK (Status IN (0,1,2))→ 保留SQLite 支持 CHECKROWVERSION→ 删除SQLite 无等价类型建议用INTEGER DEFAULT (strftime(%s,now))替代生成 SQLite DDLCREATE TABLE Orders ( Id INTEGER PRIMARY KEY AUTOINCREMENT, OrderNo TEXT NOT NULL, CreatedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, Status INTEGER CHECK (Status IN (0,1,2)) );关键点IDENTITY(1,1)被转为INTEGER PRIMARY KEY AUTOINCREMENT但 SQLite 的AUTOINCREMENT有性能开销实际生产中常简化为INTEGER PRIMARY KEY隐式自增。项目提供--no-autoincrement参数控制此行为。3. 用最小命令跑通一次完整转换从 SQL Server 备份到 SQLite 文件生成不要一上来就处理生产库。先用微软官方 AdventureWorksLT2022 示例库验证流程。假设你已安装 SQL Server Express 并还原该数据库。3.1 第一步导出结构与数据脱离 SQL Server 运行时项目不直连数据库而是依赖sqlcmd工具导出。确保sqlcmd在 PATH 中SQL Server 客户端工具自带# 导出 DDL不含数据 sqlcmd -S localhost\SQLEXPRESS -d AdventureWorksLT2022 -E -Q SELECT CREATE TABLE [ s.name ].[ t.name ] ( STRING_AGG(c.column_def, , ) ); FROM sys.tables t JOIN sys.schemas s ON t.schema_id s.schema_id CROSS APPLY (SELECT [ c.name ] TYPE_NAME(c.user_type_id) CASE WHEN c.max_length 0 AND TYPE_NAME(c.user_type_id) IN (varchar,nvarchar) THEN (CAST(c.max_length AS VARCHAR)) ELSE END CASE WHEN c.is_nullable 0 THEN NOT NULL ELSE END FROM sys.columns c WHERE c.object_id t.object_id ORDER BY c.column_id FOR XML PATH()) c(column_def) GROUP BY s.name, t.name -o aw_schema.sql # 导出数据CSV 格式兼容 SQLite 导入 bcp SELECT * FROM AdventureWorksLT2022.dbo.Product queryout product.csv -c -t, -S localhost\SQLEXPRESS -T注意bcp导出时-c表示字符模式非 Unicode-t,指定逗号分隔。若字段含逗号需改用-t|竖线分隔并在后续sqlite3导入时指定separator |。3.2 第二步用编译好的 SqlConverter 执行转换假设已成功编译出./publish-win/SqlConverter.exe执行./publish-win/SqlConverter.exe ^ --input-schema aw_schema.sql ^ --input-data product.csv ^ --table-name Product ^ --output-db aw_lite.sqlite3 ^ --log-level Debug参数说明--input-schemaSQL Server 导出的建表语句.sql文件--input-databcp导出的 CSV 文件必须与--table-name匹配--table-name目标表名用于定位 CSV 中哪张表的数据--output-db生成的 SQLite 数据库文件路径--log-level Debug输出详细日志查看每列类型如何映射成功后aw_lite.sqlite3将包含Product表且ListPriceMONEY类型被转为REALSellStartDateDATETIME2被转为TEXT。3.3 第三步验证 SQLite 输出是否可用用sqlite3CLI 验证sqlite3 aw_lite.sqlite3 # 进入交互模式后执行 .tables # 应显示 Product .schema Product # 查看建表语句是否符合预期 SELECT COUNT(*) FROM Product; # 应返回 CSV 行数 SELECT typeof(ListPrice), typeof(SellStartDate) FROM Product LIMIT 1; # 应返回 real 和 text关键验证点typeof()返回值必须与转换规则一致。若ListPrice显示text说明MONEY未被正确识别为数值类型——此时需检查aw_schema.sql中该列定义是否含MONEY关键字有些导出工具会转成DECIMAL(19,4)需在源码TypeMapping.cs中补充映射规则。4. 避坑指南那些让转换中途崩溃、数据错乱、甚至删库的 5 个真实翻车现场这个转换器不是“一键傻瓜式”SQL Server 和 SQLite 的语义差异会在细节处咬人。以下是我在三个客户现场踩过的坑按发生频率排序4.1 现象转换后 SQLite 表中datetime2字段全为NULL原因SQL Server 导出的 CSV 中datetime2列值为2023-01-01 12:34:56.7890123但 SQLite 的TEXT类型默认不校验格式SqlConverter的 CSV 解析器未启用DateTime.TryParseExact()导致无法解析微秒部分整行数据被跳过。解决修改/src/Generator/DataImporter.cs在ParseCsvRow()方法中增加if (columnType TEXT columnName.Contains(Date) || columnName.Contains(Time)) { // 强制尝试解析为 DateTime失败则存原始字符串 if (DateTime.TryParseExact(value, yyyy-MM-dd HH:mm:ss.fffffff, CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) row[columnIndex] value; // 保持字符串格式 else row[columnIndex] value.TrimEnd(0).TrimEnd(.); // 去掉末尾零 }4.2 现象UNIQUEIDENTIFIER列导入 SQLite 后变成空字符串原因SQL Server 的uniqueidentifier导出为bcpCSV 时格式为{A0EEBC99-9C0B-4EF8-BB6D-DAF45F5A1234}而SqlConverter的默认 UUID 清洗正则\{?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}?未覆盖大写字母匹配失败。解决在TypeMapping.cs的MapUniqueIdentifier()方法中将正则改为忽略大小写var match Regex.Match(value, (?i)\{?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}?);4.3 现象COMPUTED COLUMN计算列被忽略但下游应用 SQL 报错no such column原因转换器默认跳过PERSISTED计算列但未生成对应的VIEW或TRIGGER模拟计算逻辑。例如FullName AS FirstName LastName在 SQLite 中无法直接复现。解决启用--generate-views参数让转换器为每个计算列生成视图CREATE VIEW ProductView AS SELECT *, Name || ( || ProductNumber || ) AS FullName FROM Product;并在代码中修改Generator/ViewGenerator.cs添加GenerateComputedColumnViews()方法。4.4 现象NVARCHAR(MAX)导出为 CSV 时被截断为 4000 字符原因bcp默认VARCHAR最大长度为 8000 字节NVARCHAR因双字节存储实际截断在 4000 字符。SqlConverter未检测此截断直接导入导致长文本丢失。解决导出时强制指定-wUnicode 模式和-r \n行结束符bcp SELECT * FROM dbo.LongTextTable queryout longtext.csv -w -r \n -S localhost\SQLEXPRESS -T并在DataExtractor中增加 CSV 行长度校验逻辑。4.5 现象编译时报错error CS0234: The type or namespace name SqlClient does not exist in the namespace Microsoft.Data原因项目引用了Microsoft.Data.SqlClientv5.1.5但本地 NuGet cache 中存在旧版 v2.xdotnet restore未强制更新。解决清理并指定版本dotnet nuget locals all --clear dotnet restore --no-cache # 编辑 .csproj显式锁定版本 PackageReference IncludeMicrosoft.Data.SqlClient Version5.1.5 /5. 进阶技巧如何让转换器适配你的特殊场景——从参数调优到源码定制光跑通 demo 不够。真实项目总有例外客户要求datetime2存为 Unix 时间戳整数、money必须精确到分不能用REAL、XML字段要转成 JSON 字符串。这些需求无法靠配置解决必须改源码。以下是四个高频定制点附可直接抄的代码片段。5.1 把datetime2全部转为INTEGERUnix 时间戳而非TEXTSQLite 官方推荐用INTEGER存时间戳便于BETWEEN查询和索引但默认转换器只支持TEXT。修改/src/Generator/TypeMapping.cspublic static string MapDateTime2(string sqlServerType, string columnName) { // 原逻辑return TEXT; // 新逻辑转为 Unix 时间戳秒级 return INTEGER; // SQLite 中 INTEGER 可存 64 位整数 } // 同时修改 DataImporter.cs在插入前转换时间值 if (columnType INTEGER columnName.Contains(Date) || columnName.Contains(Time)) { if (DateTime.TryParse(value, out var dt)) { // 转为 Unix 时间戳秒 long timestamp ((DateTimeOffset)dt).ToUnixTimeSeconds(); row[columnIndex] timestamp.ToString(); } }注意此改动影响所有datetime2列。若只需部分列如CreatedAt在MapDateTime2()中加columnName CreatedAt判断。5.2 为money类型启用定点数支持用TEXT存DECIMAL字符串避免浮点误差REAL在 SQLite 中是 IEEE 754 双精度浮点money字段如199.99可能存为199.98999999999998。金融场景必须用字符串存DECIMAL// 在 TypeMapping.cs 中添加 public static string MapMoney(string sqlServerType, string columnName) { return TEXT; // 存 199.99 字符串 } // 在 DataImporter.cs 中对 money 列做格式化 if (columnType TEXT sqlServerType money) { // 确保小数点后两位去除科学计数法 if (decimal.TryParse(value, out decimal dec)) row[columnIndex] dec.ToString(0.00); }5.3 处理XML字段自动转 JSON 并移除命名空间SQL Server 的XML类型导出为bcpCSV 时是带root.../root的字符串直接存 SQLiteTEXT不便查询。用System.Xml解析并转 JSON// 在 DataImporter.cs 中添加 if (sqlServerType xml) { try { var doc new XmlDocument(); doc.LoadXml(value); // 移除命名空间SQL Server XML 常含 xmlns var json JsonConvert.SerializeXmlNode(doc, Newtonsoft.Json.Formatting.None, true); row[columnIndex] json; } catch { row[columnIndex] value; // 解析失败则存原文 } }需在.csproj中添加PackageReference IncludeNewtonsoft.Json Version13.0.3 /5.4 生成带外键约束的 SQLite 表默认不生成因 SQLite 外键需 PRAGMA 启用SQL Server 的FOREIGN KEY在 SQLite 中需显式开启PRAGMA foreign_keys ON且CREATE TABLE中需声明REFERENCES。修改Generator/TableGenerator.csprivate string GenerateForeignKeyClause(ForeignKey fk) { // 原逻辑return ; // 新逻辑 return $, FOREIGN KEY ({fk.Column}) REFERENCES {fk.ReferencedTable}({fk.ReferencedColumn}); } // 并在生成 DB 后自动插入 PRAGMA File.AppendAllText(outputDbPath, PRAGMA foreign_keys ON;\n);最后提醒一句我曾为客户定制过“把 SQL Server 视图转为 SQLite 的CREATE VIEW”结果发现视图里用了PIVOT—— SQLite 不支持。当时花两天重写为CASE WHEN手动展开。所以别迷信“自动转换”真正的生产力来自你敢改源码的底气而不是找更完美的工具。希望帮到你。本文还有配套的精品资源点击获取