编辑
2025-09-18
C#
00

目录

🔍 问题分析:数据库迁移的三大痛点
😵 痛点1:数据类型映射复杂
😵 痛点2:表结构创建繁琐
😵 痛点3:数据同步容易出错
🛠️ 解决方案:C#自动化迁移工具
🎯 核心设计思路
💻 代码实战:完整解决方案
🔧 项目准备
🔥 核心转换器类
📊 表结构获取方法
🏗️ MySQL表创建方法
🔄 数据类型智能映射
📦 数据同步核心方法
🎯 使用示例
⚠️ 重要提醒:生产环境注意事项
🔒 1. 安全性考虑
🚀 2. 性能优化
📊 3. 监控与日志
✨ 总结:三个关键收获
🎯 1. 自动化是王道
🎯 2. 数据类型映射是核心
🎯 3. 安全性不能忽视

小李是某互联网公司的C#开发工程师,最近公司决定将项目从SQL Server迁移到MySQL以降低成本。面对200多张表的迁移任务,小李开始头疼了...

手动迁移表结构? 光是数据类型转换就能让人崩溃!

导出导入数据? 字符编码、主键冲突、数据格式不兼容...

据统计,85%的开发团队在数据库迁移过程中都遇到过数据丢失或结构错误的问题。今天这篇文章,我将分享一套完整的C#自动化解决方案,让你轻松搞定数据库迁移!

🔍 问题分析:数据库迁移的三大痛点

😵 痛点1:数据类型映射复杂

SQL Server的nvarchar(MAX)对应MySQL的什么类型?datetime2又该如何转换?

😵 痛点2:表结构创建繁琐

手动写CREATE TABLE语句?一个表还好,几百个表简直是噩梦!

😵 痛点3:数据同步容易出错

字符转义、NULL值处理、大数据量传输...每一步都是坑!

🛠️ 解决方案:C#自动化迁移工具

🎯 核心设计思路

  1. 自动获取源表结构信息
  2. 智能映射数据类型
  3. 批量创建目标表
  4. 安全同步所有数据

💻 代码实战:完整解决方案

🔧 项目准备

首先安装必要的NuGet包:

XML
System.Data.SqlClient MySql.Data

image.png

🔥 核心转换器类

C#
using System; using System.Data; using System.Data.SqlClient; using MySql.Data.MySqlClient; using System.Text; namespace DatabaseSync { public class DatabaseConverter { // 连接字符串存储 private string sqlServerConnStr; private string mysqlConnStr; public DatabaseConverter(string sqlServerConnStr, string mysqlConnStr) { this.sqlServerConnStr = sqlServerConnStr; this.mysqlConnStr = mysqlConnStr; } /// <summary> /// 一键转换主方法 - 这是整个流程的入口 /// </summary> /// <param name="tableName">要转换的表名</param> public void Convert(string tableName) { try { Console.WriteLine($"🚀 开始转换表:{tableName}"); // 步骤1:获取SQL Server表结构 DataTable schema = GetSqlServerTableSchema(tableName); Console.WriteLine("✅ 表结构获取完成"); // 步骤2:创建MySQL表 CreateMySqlTable(schema, tableName); Console.WriteLine("✅ MySQL表创建完成"); // 步骤3:同步数据 SyncData(tableName); Console.WriteLine($"🎉 表 {tableName} 转换完成!"); } catch (Exception ex) { Console.WriteLine($"❌ 转换过程出错:{ex.Message}"); throw; } } } }

📊 表结构获取方法

C#
/// <summary> /// 从SQL Server获取完整表结构信息 /// 包括:列名、数据类型、长度、是否允许NULL、是否自增等 /// </summary> private DataTable GetSqlServerTableSchema(string tableName) { using (SqlConnection conn = new SqlConnection(sqlServerConnStr)) { conn.Open(); // 🔍 关键SQL:获取表的完整结构信息 string query = @" SELECT COLUMN_NAME, -- 列名 DATA_TYPE, -- 数据类型 CHARACTER_MAXIMUM_LENGTH, -- 最大长度 IS_NULLABLE, -- 是否允许NULL COLUMN_DEFAULT, -- 默认值 CASE WHEN COLUMNPROPERTY(OBJECT_ID(@TableName), COLUMN_NAME, 'IsIdentity') = 1 THEN 'YES' ELSE 'NO' END AS IS_IDENTITY -- 是否自增 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName ORDER BY ORDINAL_POSITION"; using (SqlCommand cmd = new SqlCommand(query, conn)) { // 🛡️ 使用参数化查询防止SQL注入 cmd.Parameters.AddWithValue("@TableName", tableName); DataTable schema = new DataTable(); schema.Load(cmd.ExecuteReader()); return schema; } } }

🏗️ MySQL表创建方法

C#
/// <summary> /// 根据SQL Server表结构创建对应的MySQL表 /// 自动处理数据类型映射和约束转换 /// </summary> private void CreateMySqlTable(DataTable schema, string tableName) { using (MySqlConnection conn = new MySqlConnection(mysqlConnStr)) { conn.Open(); StringBuilder createTableSql = new StringBuilder(); createTableSql.AppendLine($"CREATE TABLE IF NOT EXISTS `{tableName}` ("); // 🔄 遍历所有列,构建CREATE TABLE语句 for (int i = 0; i < schema.Rows.Count; i++) { DataRow row = schema.Rows[i]; string columnName = row["COLUMN_NAME"].ToString(); string dataType = row["DATA_TYPE"].ToString(); string maxLength = row["CHARACTER_MAXIMUM_LENGTH"].ToString(); string isNullable = row["IS_NULLABLE"].ToString(); string isIdentity = row["IS_IDENTITY"].ToString(); // 构建列定义 createTableSql.Append($"`{columnName}` {ConvertDataType(dataType, maxLength)}"); // 🚀 处理自增属性 if (isIdentity == "YES") { createTableSql.Append(" AUTO_INCREMENT"); } // 🔒 处理NULL约束 if (isNullable == "NO") { createTableSql.Append(" NOT NULL"); } // 添加逗号分隔符(最后一列除外) if (i < schema.Rows.Count - 1) { createTableSql.AppendLine(","); } } // 🔑 添加主键(假设第一列为主键) createTableSql.AppendLine($",PRIMARY KEY (`{schema.Rows[0]["COLUMN_NAME"]}`)"); createTableSql.AppendLine(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); using (MySqlCommand cmd = new MySqlCommand(createTableSql.ToString(), conn)) { cmd.ExecuteNonQuery(); } } }

🔄 数据类型智能映射

C#
/// <summary> /// SQL Server数据类型到MySQL数据类型的智能映射 /// 这是迁移成功的关键! /// </summary> private string ConvertDataType(string sqlServerType, string maxLength, bool isPrimaryKey = false) { switch (sqlServerType.ToLower()) { // 🔢 整数类型映射 case "int": return "int"; case "bigint": return "bigint"; case "smallint": return "smallint"; case "tinyint": return "tinyint"; case "bit": return "bit"; // 💰 数值类型映射 case "decimal": case "numeric": return "decimal(18,2)"; case "float": return "float"; // 📅 日期时间类型映射 case "datetime": case "datetime2": return "datetime"; case "date": return "date"; case "time": return "time"; // 📝 字符串类型映射 case "char": case "nchar": return $"char({maxLength})"; case "varchar": case "nvarchar": if (maxLength == "-1") // MAX类型 { // 如果是主键,限制长度 return isPrimaryKey ? "varchar(255)" : "text"; } else { int length = int.Parse(maxLength); // 如果是主键且长度过大,限制为255 if (isPrimaryKey && length > 255) { return "varchar(255)"; } return $"varchar({maxLength})"; } case "text": case "ntext": // 如果是主键,使用varchar(255) return isPrimaryKey ? "varchar(255)" : "text"; case "uniqueidentifier": return "varchar(36)"; // 🔧 默认处理 default: return isPrimaryKey ? "varchar(255)" : "text"; } }

📦 数据同步核心方法

C#
/// <summary> /// 核心数据同步方法 - 处理大数据量迁移 /// 包含字符转义、NULL值处理等关键逻辑 /// </summary> private void SyncData(string tableName) { using (SqlConnection sqlConn = new SqlConnection(sqlServerConnStr)) using (MySqlConnection mysqlConn = new MySqlConnection(mysqlConnStr)) { sqlConn.Open(); mysqlConn.Open(); // 📖 读取源数据 using (SqlCommand sqlCmd = new SqlCommand($"SELECT * FROM {tableName}", sqlConn)) using (SqlDataReader reader = sqlCmd.ExecuteReader()) { DataTable schemaTable = reader.GetSchemaTable(); if (reader.HasRows) { MySqlCommand mysqlCmd = new MySqlCommand(); mysqlCmd.Connection = mysqlConn; int recordCount = 0; // 🔄 逐行处理数据 while (reader.Read()) { StringBuilder insertSql = new StringBuilder(); insertSql.Append($"INSERT INTO `{tableName}` ("); // 🏷️ 构建列名部分 for (int i = 0; i < schemaTable.Rows.Count; i++) { string columnName = schemaTable.Rows[i]["ColumnName"].ToString(); insertSql.Append($"`{columnName}`"); if (i < schemaTable.Rows.Count - 1) insertSql.Append(","); } insertSql.Append(") VALUES ("); // 💾 构建值部分 for (int i = 0; i < reader.FieldCount; i++) { if (reader.IsDBNull(i)) { insertSql.Append("NULL"); } else { // 🔍 获取列的数据类型 string dataTypeName = schemaTable.Rows[i]["DataTypeName"].ToString(); object value = reader.GetValue(i); // 📅 特殊处理日期时间类型 if (IsDateTimeType(dataTypeName)) { DateTime dateTime = System.Convert.ToDateTime(value); // 转换为MySQL标准格式:YYYY-MM-DD HH:mm:ss string formattedDate = dateTime.ToString("yyyy-MM-dd HH:mm:ss"); insertSql.Append($"'{formattedDate}'"); } else { if(dataTypeName == "bit") { var bitValue = value.ToString()=="true" ? 1 : 0; insertSql.Append($"{bitValue}"); } else { string stringValue = value.ToString(); // 🛡️ SQL注入防护 - 转义单引号 insertSql.Append($"'{stringValue.Replace("'", "''")}'"); } } } if (i < reader.FieldCount - 1) insertSql.Append(","); } insertSql.Append(")"); mysqlCmd.CommandText = insertSql.ToString(); mysqlCmd.ExecuteNonQuery(); recordCount++; // 📊 进度提示 if (recordCount % 1000 == 0) { Console.WriteLine($"已处理 {recordCount} 条记录..."); } } Console.WriteLine($"✅ 数据同步完成,共处理 {recordCount} 条记录"); } } } } /// <summary> /// 判断是否为日期时间类型 /// </summary> private bool IsDateTimeType(string dataTypeName) { string[] dateTimeTypes = { "DateTime", "DateTime2", "Date", "Time", "SmallDateTime", "DateTimeOffset" }; return dateTimeTypes.Contains(dataTypeName, StringComparer.OrdinalIgnoreCase); }

🎯 使用示例

C#
using System.Text; namespace AppSqlserver2Mysql { internal class Program { static void Main(string[] args) { Console.InputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8; try { // 🔗 配置连接字符串 string sqlServerConnStr = "Server=.;Database=**;Trusted_Connection=True;"; string mysqlConnStr = "Server=***;Database=test;Uid=root;Pwd=Iseeyou123;"; // 🏭 创建转换器实例 DatabaseConverter converter = new DatabaseConverter(sqlServerConnStr, mysqlConnStr); // 🚀 开始转换(可以批量处理多个表) string[] tablesToConvert = { "wms_user", "wms_basic_material", "wms_basic_location" }; foreach (string tableName in tablesToConvert) { converter.Convert(tableName); Console.WriteLine($"✅ {tableName} 迁移完成!"); } Console.WriteLine("🎉 所有表迁移完成!"); } catch (Exception ex) { Console.WriteLine($"❌ 程序执行失败:{ex.Message}"); } } } }

image.png

⚠️ 重要提醒:生产环境注意事项

🔒 1. 安全性考虑

  • 连接字符串加密:生产环境中绝不要明文存储数据库密码
  • 权限最小化:使用专门的迁移账户,只授予必要权限

🚀 2. 性能优化

  • 批量处理:对于大表,建议分批次处理避免内存溢出
  • 事务控制:添加事务确保数据一致性
C#
// 💡 性能优化示例:批量插入 private void SyncDataInBatches(string tableName, int batchSize = 1000) { // 使用MySQL的批量插入语法提升性能 // INSERT INTO table VALUES (1,'a'),(2,'b'),(3,'c')... }

📊 3. 监控与日志

  • 进度跟踪:实时显示迁移进度
  • 错误记录:详细记录失败的表和原因
  • 数据校验:迁移完成后对比记录数确保完整性

✨ 总结:三个关键收获

🎯 1. 自动化是王道

手动迁移100张表?不如花2小时写个工具,让程序跑一夜!

🎯 2. 数据类型映射是核心

掌握SQL Server到MySQL的类型转换规则,这是迁移成功的关键。

🎯 3. 安全性不能忽视

字符转义、参数化查询、权限控制...每一个细节都关乎数据安全。上面例子其实最好参数化会好不少。


💬 互动时间

  1. 你们团队在数据库迁移时遇到过哪些坑?
  2. 除了SQL Server到MySQL,还需要其他数据库的迁移方案吗?

觉得这篇文章对你有帮助的话,记得转发给更多需要的同行!让我们一起告别手动迁移的痛苦时代!🚀

相关信息

通过网盘分享的文件:AppSqlserver2Mysql.zip 链接: https://pan.baidu.com/s/1a_5-xw2pQd8jwFiiztDNNA?pwd=serw 提取码: serw --来自百度网盘超级会员v9的分享

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!