using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.IO; using System.Linq; using System.Text; using MySql.Data.MySqlClient; using Newtonsoft.Json; namespace DataMigration { class Program { static void Main(string[] args) { Console.WriteLine("开始数据迁移..."); try { // 读取配置 var config = ReadConfig(); // 迁移TugboatCommon数据库 Console.WriteLine("\n迁移TugboatCommon数据库..."); MigrateTugboatCommon(config); // 迁移LiandaTugboatMIS数据库 Console.WriteLine("\n迁移LiandaTugboatMIS数据库..."); MigrateLiandaTugboatMIS(config); Console.WriteLine("\n数据迁移完成!"); } catch (Exception ex) { Console.WriteLine($"迁移过程中出错: {ex.Message}"); Console.WriteLine(ex.StackTrace); } Console.WriteLine("\n按任意键退出..."); Console.ReadKey(); } static Config ReadConfig() { var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "appsettings.json"); var json = File.ReadAllText(configPath); return JsonConvert.DeserializeObject(json); } static void MigrateTugboatCommon(Config config) { using (var sqlConn = new SqlConnection(config.ConnectionStrings.SqlServerCommonDataContext)) using (var mysqlConn = new MySqlConnection(config.ConnectionStrings.MySqlTugboatCommon)) { sqlConn.Open(); mysqlConn.Open(); Console.WriteLine("连接数据库成功"); // 动态获取SQL Server中的所有表名 var tablesToMigrate = GetAllSqlServerTables(sqlConn); Console.WriteLine($"在SQL Server中找到{tablesToMigrate.Count}个表:"); foreach (var tableName in tablesToMigrate) { Console.WriteLine($"- {tableName}"); } // 首先删除所有表,以避免外键约束的干扰 DropAllTables(mysqlConn, tablesToMigrate); // 迁移所有表 foreach (var tableName in tablesToMigrate) { MigrateTable(sqlConn, mysqlConn, tableName); } Console.WriteLine("TugboatCommon数据库迁移完成"); } } static void DropAllTables(MySqlConnection conn, List tables) { Console.WriteLine("\n开始删除所有表..."); // 禁用外键约束检查 using (var cmd = new MySqlCommand("SET FOREIGN_KEY_CHECKS = 0", conn)) { cmd.ExecuteNonQuery(); } // 禁用唯一约束检查 using (var cmd = new MySqlCommand("SET UNIQUE_CHECKS = 0", conn)) { cmd.ExecuteNonQuery(); } // 逆序删除表,以避免外键约束问题 for (int i = tables.Count - 1; i >= 0; i--) { string tableName = tables[i]; try { using (var cmd = new MySqlCommand($"DROP TABLE IF EXISTS {tableName}", conn)) { cmd.ExecuteNonQuery(); Console.WriteLine($"删除表: {tableName}"); } } catch (Exception ex) { Console.WriteLine($"删除表{tableName}时出错: {ex.Message}"); } } Console.WriteLine("删除所有表完成"); } static void MigrateLiandaTugboatMIS(Config config) { using (var sqlConn = new SqlConnection(config.ConnectionStrings.SqlServerDataContext)) using (var mysqlConn = new MySqlConnection(config.ConnectionStrings.MySqlLiandaTugboatMIS)) { sqlConn.Open(); mysqlConn.Open(); Console.WriteLine("连接数据库成功"); // 动态获取SQL Server中的所有表名 var allTables = GetAllSqlServerTables(sqlConn); Console.WriteLine($"在SQL Server中找到{allTables.Count}个表:"); foreach (var tableName in allTables) { Console.WriteLine($"- {tableName}"); } // 首先删除所有表,以避免外键约束的干扰 DropAllTables(mysqlConn, allTables); // 定义无外键依赖的表名模式 var independentTablePatterns = new List { "Sys_User", "Sys_Role", "Sys_Menu", "Sys_Function", "Sys_Dictionary", "Fin_AssistCode", "Fin_Subject", "Fin_TaxRule", "Pro_FeeItemSettings", "Xh_ReceiptDictionary", "Bus_Holiday", "Bus_ShipPaymentType", "Disp_Port", "Disp_PortDictionary", "Sal_Department", "Sal_Employee" }; // 先迁移无外键依赖的表 var independentTables = allTables.Where(t => independentTablePatterns.Any(p => t.Contains(p))).ToList(); Console.WriteLine($"\n先迁移无外键依赖的{independentTables.Count}个表:"); foreach (var tableName in independentTables) { MigrateTable(sqlConn, mysqlConn, tableName); } // 再迁移其他表(可能有外键依赖) var dependentTables = allTables.Except(independentTables).ToList(); Console.WriteLine($"\n再迁移其他{dependentTables.Count}个表:"); foreach (var tableName in dependentTables) { MigrateTable(sqlConn, mysqlConn, tableName); } Console.WriteLine("LiandaTugboatMIS数据库迁移完成"); } } static void MigrateTable(SqlConnection sqlConn, MySqlConnection mysqlConn, string tableName) { // 检查是否是Sys_Log表(操作日志,数据量很大,对系统逻辑没有太大帮助) bool isSysLogTable = tableName.Equals("Sys_Log", StringComparison.OrdinalIgnoreCase) || tableName.Equals("Sys_log", StringComparison.OrdinalIgnoreCase); Console.WriteLine($"\n迁移{tableName}表..."); try { // 从SQL Server读取表结构 DataTable schemaTable = GetSqlServerTableSchema(sqlConn, tableName); Console.WriteLine($"读取SQL Server表结构完成,包含{schemaTable.Columns.Count}列"); // 打印SQL Server表的所有列信息 Console.WriteLine("SQL Server表列信息:"); foreach (DataColumn column in schemaTable.Columns) { Console.WriteLine($" - {column.ColumnName} ({column.DataType.Name}, AllowDBNull: {column.AllowDBNull})"); } // 确保MySQL表结构与SQL Server一致 EnsureTableStructure(mysqlConn, sqlConn, tableName, schemaTable); // 如果是Sys_Log表,只迁移表结构,不迁移数据 if (isSysLogTable) { Console.WriteLine($"跳过{tableName}表的数据迁移(操作日志,数据量较大)"); return; } // 读取SQL Server数据 var dataTable = ReadTableFromSqlServer(sqlConn, tableName); Console.WriteLine($"从SQL Server读取到{dataTable.Rows.Count}条记录"); if (dataTable.Rows.Count > 0) { // 清空MySQL表 ClearMySqlTable(mysqlConn, tableName); // 写入MySQL WriteTableToMySql(mysqlConn, tableName, dataTable); Console.WriteLine($"成功写入MySQL"); } } catch (Exception ex) { if (ex.Message.Contains("对象名 '" + tableName + "' 无效")) { Console.WriteLine($"SQL Server中不存在表{tableName},跳过迁移"); } else { Console.WriteLine($"迁移过程中出错: {ex.Message}"); throw; } } } static DataTable GetSqlServerTableSchema(SqlConnection sqlConn, string tableName) { string query = $"SELECT * FROM {tableName} WHERE 1=0"; using (SqlCommand cmd = new SqlCommand(query, sqlConn)) using (SqlDataAdapter adapter = new SqlDataAdapter(cmd)) { DataTable schemaTable = new DataTable(); adapter.FillSchema(schemaTable, SchemaType.Source); return schemaTable; } } static string EscapeMySqlIdentifier(string identifier) { // MySQL保留关键字列表 var reservedKeywords = new HashSet { "MAXVALUE", "MINVALUE", "ORDER", "GROUP", "BY", "SELECT", "INSERT", "UPDATE", "DELETE", "FROM", "WHERE", "JOIN", "ON", "IN", "NOT", "AND", "OR", "LIKE", "LIMIT" }; // 如果是保留关键字,使用反引号包围 if (reservedKeywords.Contains(identifier.ToUpper())) { return $"`{identifier}`"; } return identifier; } static void EnsureTableStructure(MySqlConnection conn, SqlConnection sqlConn, string tableName, DataTable schemaTable) { Console.WriteLine($"检查{tableName}表结构..."); // 执行一系列SQL语句来禁用所有可能的约束检查 string[] disableConstraintsSql = new string[] { "SET FOREIGN_KEY_CHECKS = 0", "SET UNIQUE_CHECKS = 0", "SET SQL_MODE = 'NO_ENGINE_SUBSTITUTION'", "SET AUTOCOMMIT = 0" }; foreach (string sql in disableConstraintsSql) { try { using (var cmd = new MySqlCommand(sql, conn)) { cmd.ExecuteNonQuery(); Console.WriteLine($"执行SQL成功: {sql}"); } } catch (Exception ex) { Console.WriteLine($"执行SQL时出错: {sql}, 错误信息: {ex.Message}"); } } // 删除旧表 try { using (var cmd = new MySqlCommand($"DROP TABLE IF EXISTS {tableName}", conn)) { cmd.ExecuteNonQuery(); Console.WriteLine($"删除旧表: {tableName}"); } } catch (Exception ex) { Console.WriteLine($"删除表时出错: {ex.Message}"); } // 从SQL Server获取主键信息 var primaryKeys = GetSqlServerPrimaryKeys(sqlConn, tableName); Console.WriteLine($"从SQL Server获取到{primaryKeys.Count}个主键列: {string.Join(", ", primaryKeys)}"); // 根据SQL Server表结构动态创建MySQL表 var createTableSql = new StringBuilder(); createTableSql.AppendLine($"CREATE TABLE IF NOT EXISTS {tableName} ("); var columns = new List(); // 遍历SQL Server表的所有列 foreach (DataColumn column in schemaTable.Columns) { string columnName = EscapeMySqlIdentifier(column.ColumnName); string mysqlType = GetMySqlDataType(column.DataType); // 构建列定义 string columnDef = $" {columnName} {mysqlType}"; // 检查是否允许空值 if (!column.AllowDBNull) { columnDef += " NOT NULL"; } columns.Add(columnDef); } // 添加主键约束 if (primaryKeys.Count > 0) { var primaryKeyColumns = primaryKeys.Select(k => EscapeMySqlIdentifier(k)).ToList(); columns.Add($" PRIMARY KEY ({string.Join(", ", primaryKeyColumns)})"); Console.WriteLine($"为表{tableName}添加主键约束: {string.Join(", ", primaryKeys)}"); } else { Console.WriteLine($"表{tableName}在SQL Server中没有主键"); } // 添加列定义到创建表语句 createTableSql.AppendLine(string.Join(",\n", columns)); createTableSql.AppendLine(") ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"); // 执行创建表语句 try { Console.WriteLine($"创建表SQL: {createTableSql.ToString().Substring(0, Math.Min(500, createTableSql.Length))}..."); using (var cmd = new MySqlCommand(createTableSql.ToString(), conn)) { cmd.ExecuteNonQuery(); Console.WriteLine($"创建新表: {tableName}"); Console.WriteLine($"表结构创建成功,包含{schemaTable.Columns.Count}列"); if (primaryKeys.Count > 0) { Console.WriteLine($"已添加主键约束: {string.Join(", ", primaryKeys)}"); } } } catch (Exception ex) { Console.WriteLine($"创建表时出错: {ex.Message}"); // 如果创建表失败,尝试一个更简单的方法 Console.WriteLine("尝试使用更简单的方法创建表..."); string simpleCreateSql = $"CREATE TABLE IF NOT EXISTS {tableName} ("; // 添加所有列,但使用最简单的定义 var simpleColumns = new List(); foreach (DataColumn column in schemaTable.Columns) { string columnName = EscapeMySqlIdentifier(column.ColumnName); string mysqlType = "VARCHAR(255)"; simpleColumns.Add($" {columnName} {mysqlType}"); } // 添加主键约束 if (primaryKeys.Count > 0) { var primaryKeyColumns = primaryKeys.Select(k => EscapeMySqlIdentifier(k)).ToList(); simpleColumns.Add($" PRIMARY KEY ({string.Join(", ", primaryKeyColumns)})"); } simpleCreateSql += string.Join(",\n", simpleColumns); simpleCreateSql += ") ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"; try { using (var cmd = new MySqlCommand(simpleCreateSql, conn)) { cmd.ExecuteNonQuery(); Console.WriteLine($"使用简单方法创建表成功: {tableName}"); if (primaryKeys.Count > 0) { Console.WriteLine($"已添加主键约束: {string.Join(", ", primaryKeys)}"); } } } catch (Exception simpleEx) { Console.WriteLine($"使用简单方法创建表时出错: {simpleEx.Message}"); // 如果仍然失败,创建一个只有ID列的表 try { string minimalCreateSql = $"CREATE TABLE IF NOT EXISTS {tableName} (ID VARCHAR(36) PRIMARY KEY) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"; using (var cmd = new MySqlCommand(minimalCreateSql, conn)) { cmd.ExecuteNonQuery(); Console.WriteLine($"使用最小方法创建表成功: {tableName}"); Console.WriteLine("已添加默认主键约束: ID"); } } catch (Exception minimalEx) { Console.WriteLine($"使用最小方法创建表时出错: {minimalEx.Message}"); // 如果所有方法都失败,抛出异常 throw new Exception($"无法创建表{tableName},请检查数据库配置和权限", minimalEx); } } } // 提交事务 try { using (var cmd = new MySqlCommand("COMMIT", conn)) { cmd.ExecuteNonQuery(); Console.WriteLine("提交事务成功"); } } catch (Exception ex) { Console.WriteLine($"提交事务时出错: {ex.Message}"); } // 重新启用自动提交 try { using (var cmd = new MySqlCommand("SET AUTOCOMMIT = 1", conn)) { cmd.ExecuteNonQuery(); Console.WriteLine("重新启用自动提交成功"); } } catch (Exception ex) { Console.WriteLine($"重新启用自动提交时出错: {ex.Message}"); } } static List GetSqlServerPrimaryKeys(SqlConnection conn, string tableName) { var primaryKeys = new List(); try { // 使用sp_pkeys系统存储过程获取主键信息,这是获取表主键最可靠的方法 using (var cmd = new SqlCommand("sp_pkeys", conn)) { cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.AddWithValue("@table_name", tableName); cmd.Parameters.AddWithValue("@table_owner", DBNull.Value); cmd.Parameters.AddWithValue("@table_qualifier", DBNull.Value); Console.WriteLine($"执行存储过程获取主键: sp_pkeys @table_name = {tableName}"); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { string columnName = reader["COLUMN_NAME"].ToString(); primaryKeys.Add(columnName); Console.WriteLine($"找到主键列: {columnName}"); } } } // 如果sp_pkeys没有返回结果,尝试使用sys系统视图 if (primaryKeys.Count == 0) { Console.WriteLine("sp_pkeys未返回结果,尝试使用sys系统视图获取主键"); string query = @" SELECT col.name AS COLUMN_NAME FROM sys.indexes AS idx INNER JOIN sys.index_columns AS ic ON idx.object_id = ic.object_id AND idx.index_id = ic.index_id INNER JOIN sys.columns AS col ON ic.object_id = col.object_id AND ic.column_id = col.column_id INNER JOIN sys.tables AS tab ON col.object_id = tab.object_id WHERE idx.is_primary_key = 1 AND tab.name = @TableName ORDER BY ic.key_ordinal "; using (var cmd = new SqlCommand(query, conn)) { cmd.Parameters.AddWithValue("@TableName", tableName); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { string columnName = reader["COLUMN_NAME"].ToString(); primaryKeys.Add(columnName); Console.WriteLine($"从sys视图找到主键列: {columnName}"); } } } } Console.WriteLine($"主键获取完成,共找到{primaryKeys.Count}个主键列"); } catch (Exception ex) { Console.WriteLine($"获取SQL Server主键信息时出错: {ex.Message}"); Console.WriteLine($"错误堆栈: {ex.StackTrace}"); } return primaryKeys; } static string GetMySqlDataType(Type sqlServerType) { if (sqlServerType == typeof(Guid) || sqlServerType == typeof(string)) { return "VARCHAR(36)"; } else if (sqlServerType == typeof(int)) { return "INT"; } else if (sqlServerType == typeof(DateTime)) { return "DATETIME"; } else if (sqlServerType == typeof(decimal)) { return "DECIMAL(18, 2)"; } else if (sqlServerType == typeof(bool)) { return "BOOLEAN"; } else { return "VARCHAR(255)"; } } static List GetAllSqlServerTables(SqlConnection conn) { var tables = new List(); using (var cmd = new SqlCommand("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_CATALOG = DB_NAME()", conn)) using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { tables.Add(reader["TABLE_NAME"].ToString()); } } return tables; } static DataTable ReadTableFromSqlServer(SqlConnection conn, string tableName) { var dt = new DataTable(); using (var cmd = new SqlCommand($"SELECT * FROM {tableName}", conn)) using (var adapter = new SqlDataAdapter(cmd)) { adapter.Fill(dt); } // 调试信息:检查返回的列 if (tableName == "Sys_Menu") { Console.WriteLine($"Sys_Menu表返回的列数: {dt.Columns.Count}"); Console.WriteLine("列名列表:"); foreach (DataColumn column in dt.Columns) { Console.WriteLine($"- {column.ColumnName}"); } } return dt; } static void ClearMySqlTable(MySqlConnection conn, string tableName) { // 禁用外键约束 using (var cmd = new MySqlCommand("SET FOREIGN_KEY_CHECKS = 0", conn)) { cmd.ExecuteNonQuery(); } try { // 清空表 using (var cmd = new MySqlCommand($"TRUNCATE TABLE {tableName}", conn)) { cmd.ExecuteNonQuery(); } } finally { // 启用外键约束 using (var cmd = new MySqlCommand("SET FOREIGN_KEY_CHECKS = 1", conn)) { cmd.ExecuteNonQuery(); } } } static void WriteTableToMySql(MySqlConnection conn, string tableName, DataTable dataTable) { if (dataTable.Rows.Count == 0) return; Console.WriteLine($"开始写入{dataTable.Rows.Count}条记录到{tableName}表..."); // 构建列名列表 var columns = new List(); for (int i = 0; i < dataTable.Columns.Count; i++) { columns.Add(EscapeMySqlIdentifier(dataTable.Columns[i].ColumnName)); } var columnList = string.Join(", ", columns); // 批量插入的大小 const int batchSize = 1000; int totalRows = dataTable.Rows.Count; int processedRows = 0; using (var transaction = conn.BeginTransaction()) { try { // 分批处理数据 while (processedRows < totalRows) { // 计算当前批次的行数 int currentBatchSize = Math.Min(batchSize, totalRows - processedRows); // 构建批量插入语句 var insertSqlBuilder = new StringBuilder(); insertSqlBuilder.AppendLine($"INSERT INTO {tableName} ({columnList}) VALUES"); // 构建值列表 var valuesList = new List(); for (int i = 0; i < currentBatchSize; i++) { var row = dataTable.Rows[processedRows + i]; var valueBuilder = new StringBuilder("("); for (int j = 0; j < dataTable.Columns.Count; j++) { var value = row[j] == DBNull.Value ? null : row[j]; if (value is System.Guid) { valueBuilder.Append($"'{value.ToString().Replace("'", "''")}'"); } else if (value is string) { valueBuilder.Append($"'{value.ToString().Replace("'", "''")}'"); } else if (value is DateTime) { valueBuilder.Append($"'{((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss")}'"); } else if (value is bool) { valueBuilder.Append(((bool)value) ? "1" : "0"); } else if (value is byte[]) { // 对于字节数组,使用NULL值 valueBuilder.Append("NULL"); } else if (value == null) { valueBuilder.Append("NULL"); } else { valueBuilder.Append(value.ToString()); } if (j < dataTable.Columns.Count - 1) { valueBuilder.Append(", "); } } valueBuilder.Append(")"); valuesList.Add(valueBuilder.ToString()); } insertSqlBuilder.AppendLine(string.Join(",\n", valuesList)); string insertSql = insertSqlBuilder.ToString(); // 执行批量插入 using (var cmd = new MySqlCommand(insertSql, conn, transaction)) { cmd.ExecuteNonQuery(); } // 更新处理进度 processedRows += currentBatchSize; // 显示进度 if (processedRows % 10000 == 0 || processedRows == totalRows) { Console.WriteLine($"已处理{processedRows}/{totalRows}条记录..."); } } transaction.Commit(); Console.WriteLine($"成功写入{totalRows}条记录到{tableName}表"); } catch (Exception ex) { transaction.Rollback(); throw new Exception($"写入{tableName}表时出错: {ex.Message}", ex); } } } } class Config { public ConnectionStrings ConnectionStrings { get; set; } } class ConnectionStrings { public string SqlServerDataContext { get; set; } public string SqlServerCommonDataContext { get; set; } public string MySqlTugboatCommon { get; set; } public string MySqlLiandaTugboatMIS { get; set; } } }