最近公司决定有一个项目从SQL Server迁移到MySQL以降低成本。面对200多张表的迁移任务,头疼了…,下次有时间改成ui的版本。
手动迁移表结构? 光是数据类型转换就能让人崩溃!
导出导入数据? 字符编码、主键冲突、数据格式不兼容…
在数据库迁移过程中都遇到过数据丢失或结构错误的问题。今天这篇文章,我将分享一套完整的C#自动化解决方案,让你轻松搞定数据库迁移!
🔍 问题分析:数据库迁移的三大痛点
😵 痛点1:数据类型映射复杂
SQL Server的nvarchar(MAX)对应MySQL的什么类型?datetime2又该如何转换?
😵 痛点2:表结构创建繁琐
手动写CREATE TABLE语句?一个表还好,几百个表简直是噩梦!
😵 痛点3:数据同步容易出错
字符转义、NULL值处理、大数据量传输…每一步都是坑!
🛠️ 解决方案:C#自动化迁移工具
🎯 核心设计思路
- 自动获取
- 智能映射
- 批量创建
- 安全同步
💻 代码实战:完整解决方案
🔧 项目准备
首先安装必要的NuGet包:
System.Data.SqlClient
MySql.Data

🔥 核心转换器类
using System;
using System.Data;
using System.Data.SqlClient;
using MySql.Data.MySqlClient;
using System.Text;
namespace DatabaseSync
{
    publicclass DatabaseConverter
    {
        // 连接字符串存储
        privatestring sqlServerConnStr;
        privatestring 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;
            }
        }
    }
}
📊 表结构获取方法
/// <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表创建方法
/// <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();
        }
    }
}
🔄 数据类型智能映射
/// <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";
    }
}
📦 数据同步核心方法
/// <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);
}
🎯 使用示例
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}");
            }
        }
    }
}

⚠️ 重要提醒:生产环境注意事项
🔒 1. 安全性考虑
🚀 2. 性能优化
// 💡 性能优化示例:批量插入
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. 安全性不能忽视
字符转义、参数化查询、权限控制…每一个细节都关乎数据安全。上面例子其实最好参数化会好不少。
💬 互动时间:
- 你们团队在数据库迁移时遇到过哪些坑?
- 除了SQL Server到MySQL,还需要其他数据库的迁移方案吗?
觉得这篇文章对你有帮助的话,记得转发给更多需要的同行!让我们一起告别手动迁移的痛苦时代!🚀
阅读原文:https://mp.weixin.qq.com/s/ITJZXvzX3rwdn6t1Q6iyLQ
该文章在 2025/7/10 15:31:26 编辑过