using Learun.Application.TwoDevelopment.ZZDT_EC; using MySql.Data.MySqlClient; using SqlSugar; using System; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Configuration; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Documents.DocumentStructures; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Xml.Linq; using System.Xml.Schema; using Path = System.IO.Path; namespace SWSDBSchemeUpgradeTool { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; #region binding private string _configPath; public string configPath { get { return _configPath; } set { _configPath = value; } } private string _basedb; public string basedb { get { return _basedb; } set { _basedb = value; } } private string _curVersion; public string curVersion { get { return _curVersion; } set { _curVersion = value; } } #endregion private string dbName = ""; /// /// 是否被初始化过。没有的话,就要去执行sws.sql。 /// private bool bInitialed; /// /// (iis文件夹根目录) /// private string IISDirectory; /// /// exe的目录,会带一个\在后面 /// private string baseDirectory; string logFileName = "sync_errors.log"; string Initial_Data_sql = "Initial_Data.sql"; public MainWindow() { InitializeComponent(); ContentGrid.DataContext = this; ReadDBConfig(); ConnDB(); IfInitialed(); } /// /// 读取database.config文件 /// private void ReadDBConfig() { baseDirectory = AppDomain.CurrentDomain.BaseDirectory; // 获取上一级目录(iis文件夹根目录) IISDirectory = Directory.GetParent(baseDirectory.TrimEnd(Path.DirectorySeparatorChar)).FullName; configPath = IISDirectory + "/XmlConfig/" + "database.config"; if (System.IO.File.Exists(configPath)) { XDocument xDoc = XDocument.Load(configPath); var node = xDoc.Descendants("connectionStrings").Elements("add").FirstOrDefault(); if (node == null) { //不存在 MessageBox.Show("未能在database.config中的connectionStrings - add路径下找到合法的connectionString属性。请确认!"); Application.Current.Shutdown(); } basedb = node.Attribute("connectionString").Value; } else { //不存在 MessageBox.Show("未能在同目录XmlConfig文件夹下找到database.config文件。请确认!"); Application.Current.Shutdown(); } } /// /// 连接到数据库 /// private void ConnDB() { SqlSugarHelper.Db = new SqlSugarScope(new ConnectionConfig() { ConfigId = "0", ConnectionString = basedb,//连接符字串 DbType = DbType.MySql,//数据库类型 IsAutoCloseConnection = true //不设成true要手动close }, db => { //(A)全局生效配置点,一般AOP和程序启动的配置扔这里面 ,所有上下文生效 //调试SQL事件,可以删掉 db.Aop.OnLogExecuting = (sql, pars) => { //获取原生SQL推荐 5.1.4.63 性能OK Console.WriteLine(UtilMethods.GetNativeSql(sql, pars)); //获取无参数化SQL 对性能有影响,特别大的SQL参数多的,调试使用 //Console.WriteLine(UtilMethods.GetSqlString(DbType.SqlServer,sql,pars)) }; //多个配置就写下面 //db.Ado.IsDisableMasterSlaveSeparation=true; //注意多租户 有几个设置几个 //db.GetConnection(i).Aop }); // 尝试找到database=的部分 int startIndex = basedb.IndexOf("database="); if (startIndex != -1) { // 查找下一个分号的位置,以分割出database的值 int endIndex = basedb.IndexOf(";", startIndex); if (endIndex == -1) { // 如果没有找到分号,则取字符串的剩余部分 endIndex = basedb.Length; } // 提取database的值 dbName = basedb.Substring(startIndex + "database=".Length, endIndex - startIndex - "database=".Length); } else { MessageBox.Show("Database name not found in the connect string.请确认!"); Application.Current.Shutdown(); } } private void IfInitialed() { var temp = SqlSugarHelper.Db.Ado.GetDataTable($@"SELECT * FROM information_schema.TABLES WHERE TABLE_SCHEMA = '{dbName}' AND TABLE_NAME = 'ec_business_table';"); if (temp.Rows.Count > 0) { curVersion = $"数据库名:{dbName}"; bInitialed = true; } else { curVersion = "当前数据库还未初始化!"; bInitialed = false; } } void ExecuteVersionedScripts(string scriptsBasePath) { // 获取所有版本文件夹,并按版本顺序排序 var versionFolders = Directory.GetDirectories(scriptsBasePath) .OrderBy(v => v) .ToList(); foreach (var versionFolder in versionFolders) { //分sub和nonsub2个子文件夹 // 获取版本号 string version = new DirectoryInfo(versionFolder).Name; var subFolders = Directory.GetDirectories(Path.Combine(scriptsBasePath, version)).ToList(); foreach (var subFolder in subFolders) { // 获取该版本文件夹下的所有脚本 string[] sqlFiles = Directory.GetFiles(subFolder, "*.sql").OrderBy(f => f).ToArray(); string subFolderName = new DirectoryInfo(subFolder).Name; bool ProjectRelated = true; if (subFolderName.ToLower().Contains("no")) { //和Project无关 ProjectRelated = false; } else { ProjectRelated = true; //和Project相关 } // 检查该版本的所有脚本是否都已执行 if (!AreAllScriptsExecuted(version, sqlFiles)) { foreach (var file in sqlFiles) { string scriptName = System.IO.Path.GetFileName(file); // 如果该脚本未执行,则执行它 if (!IsScriptExecuted(version, scriptName)) { string sql = File.ReadAllText(file); //using MySqlCommand cmd = new MySqlCommand(sql, conn); //cmd.ExecuteNonQuery(); try { CompareSchemas2(sql, ProjectRelated, scriptName); // 记录该脚本的执行 RecordScriptExecution(version, scriptName); } catch (MySqlException ex) { LogError(baseDirectory + logFileName, ex, sql); } } } } } } } static void LogError(string logFilePath, MySqlException ex, string info) { StreamWriter log = new StreamWriter(logFilePath, append: true); log.WriteLine("Timestamp: " + DateTime.Now); log.WriteLine("SQL Error Number: " + ex.Number); log.WriteLine("Error Message: " + ex.Message); log.WriteLine("SQL involved: " + info); log.WriteLine("---------------------------------------------------"); log.Close(); } static void LogInfo(string logFilePath, string info) { StreamWriter log = new StreamWriter(logFilePath, append: true); log.WriteLine("Timestamp: " + DateTime.Now); log.WriteLine("Warning Message: " + info); log.WriteLine("---------------------------------------------------"); log.Close(); } // 检查版本下的所有脚本是否都已执行 bool AreAllScriptsExecuted(string version, string[] sqlFiles) { foreach (var file in sqlFiles) { string scriptName = System.IO.Path.GetFileName(file); if (!IsScriptExecuted(version, scriptName)) { return false; // 如果有一个脚本未执行,则返回false } } return true; } // 检查单个脚本是否已执行 bool IsScriptExecuted(string version, string scriptName) { string query = $"SELECT COUNT(1) FROM SchemaVersion WHERE Version = '{version}' AND ScriptName = '{scriptName}';"; //using MySqlCommand cmd = new MySqlCommand(query, conn); //cmd.Parameters.AddWithValue("@Version", version); //cmd.Parameters.AddWithValue("@ScriptName", scriptName); var temp = SqlSugarHelper.Db.Ado.GetDataTable(query); return Convert.ToInt32(temp.Rows[0][0]) > 0; } // 记录脚本执行情况 void RecordScriptExecution(string version, string scriptName) { string query = $"INSERT INTO SchemaVersion (Version, ScriptName, UpdateTime) VALUES ('{version}', '{scriptName}', NOW());"; //using MySqlCommand cmd = new MySqlCommand(query, conn); //cmd.Parameters.AddWithValue("@Version", version); //cmd.Parameters.AddWithValue("@ScriptName", scriptName); //cmd.ExecuteNonQuery(); SqlSugarHelper.Db.Ado.ExecuteCommand(query); } /// /// 增补更新。去找SQL文件夹下,每个小sql的内容 /// /// /// private void Button_Click(object sender, RoutedEventArgs e) { if (bInitialed) { if (!Directory.Exists(IISDirectory + "/" + "SQL")) { MessageBox.Show($"IIS根目录({IISDirectory})下的SQL文件夹不存在。请检查。"); return; } ExecuteVersionedScripts(IISDirectory + "/" + "SQL"); } MessageBox.Show("SQL synchronization completed. Please check the log file for any errors: " + baseDirectory + logFileName); } private async Task UpdateProgress(string content, int value) { StatusText.Content = content; ProgressBar.Value = value; await Task.Delay(200); // Simulating work } private async Task UpdateText(string content) { StatusText2.Text = content.Replace("\n", " "); } private async void Button_Click_1(object sender, RoutedEventArgs e) { await UpdateProgress("开始解析原始SQL脚本。。。", 20); var allSqlTb = ExtractTables(); await UpdateProgress("开始查询当前数据库内的状态。。。", 20); var allDbTb = GetDatabaseTableStructures(); await UpdateProgress("开始比较SQL脚本和数据库的差异。。。", 40); var diffcontent = CompareSchemas(allSqlTb, allDbTb); await UpdateProgress("开始生成需要的SQL语句。。。", 60); var fixedSQL = GenerateFixSql(diffcontent, allSqlTb); await UpdateProgress("SQL语句已准备好,请预览。。。", 80); //预览 SQLPreview secondWindow = new SQLPreview(fixedSQL); secondWindow.ShowDialog(); var chooseRes = MessageBox.Show("是否要开始进行SQL语句?", "询问", MessageBoxButton.YesNo, MessageBoxImage.Question); if (chooseRes == MessageBoxResult.Yes) { await UpdateProgress("开始进行SQL语句执行。。。", 90); await ExecuteComparisonSQL(fixedSQL); await UpdateProgress("完成。", 100); MessageBox.Show("Initial Database Structure comparison completed. Please check the log file for any errors: "); } else { } await UpdateProgress("", 0); } /// /// 解析、抽取初始脚本的SQL(表) /// /// List ExtractTables() { string sqlFilePath = IISDirectory + "/" + "SQL" + "/" + Initial_Data_sql; // Update with the correct file path string sqlContent = File.ReadAllText(sqlFilePath); var tables = new List
(); // Match each CREATE TABLE statement string tablePattern = @"CREATE\s+TABLE\s+`?(?\w+)`?\s*\((?[\s\S]*?)\)\s*ENGINE"; var tableMatches = Regex.Matches(sqlContent, tablePattern); foreach (Match tableMatch in tableMatches) { var tableName = tableMatch.Groups["TableName"].Value; var columnsDefinition = tableMatch.Groups["Columns"].Value; var table = new Table { Name = tableName, Columns = ExtractColumns(columnsDefinition) }; tables.Add(table); } return tables; } /// /// 解析、抽取初始脚本的SQL(表中的列) /// /// /// public static List ExtractColumns(string columnsDefinition) { var columns = new List(); //string columnPattern = @"`(?\w+)`\s+(?\w+\(\d+\))\s+(CHARACTER\s+SET\s+\w+\s+COLLATE\s+\w+\s+)?(?NOT\s+NULL|NULL)"; string columnPattern = @"`(?\w+)`\s+(?\w+(\(\d+\))?)(\s+CHARACTER\s+SET\s+\w+\s+COLLATE\s+\w+)?\s+(?NOT\s+NULL|NULL)"; // Match each column definition inside the CREATE TABLE statement // string columnPattern = @"`(?\w+)`\s+\w+";// @"`(?\w+)`\s+(?[^,]+),?"; var columnMatches = Regex.Matches(columnsDefinition, columnPattern); foreach (Match match in columnMatches) { //var columnName = columnMatch.Groups["ColumnName"].Value; //var columnType = columnMatch.Groups["ColumnType"].Value; //columns.Add($"{columnName} {columnType}"); var columnInfo = new ColumnInfo { ColumnName = match.Groups["ColumnName"].Value, DataType = match.Groups["DataType"].Value, IsNullable = match.Groups["Nullability"].Value != "NOT NULL" }; columns.Add(columnInfo); //columns.Add(columnMatch.Groups["column"].Value); } return columns; } /// /// 读取当前数据库里的表结构 /// /// public List
GetDatabaseTableStructures() { var tableStructures = new List
(); var tables = SqlSugarHelper.Db.Ado.GetDataTable($@"SELECT * FROM information_schema.TABLES WHERE TABLE_SCHEMA = '{dbName}';"); foreach (System.Data.DataRow row in tables.Rows) { string tableName = row["TABLE_NAME"].ToString(); //var columns = new List(); //using (var command = new MySqlCommand($"DESCRIBE {tableName}", connection)) //using (var reader = command.ExecuteReader()) //{ // while (reader.Read()) // { // columns.Add(reader["Field"].ToString()); // } //} //tableStructures.Add(tableName, columns); var TbColumns = new List(); var columns = SqlSugarHelper.Db.Ado.GetDataTable($"DESCRIBE {tableName}"); foreach (System.Data.DataRow row2 in columns.Rows) { TbColumns.Add(new ColumnInfo() { ColumnName = row2["Field"].ToString(), DataType = row2["Type"].ToString(), IsNullable = row2["Null"].ToString() != "NO" }); } tableStructures.Add(new Table() { Name = tableName, Columns = TbColumns }); } return tableStructures; } /// /// 比较sql抽取出来的表结构和当前数据库里的(增补sql小文件) /// /// /// /// public List CompareSchemas2(string sql, bool ProjectRelated,string SQLFILENAME) { var discrepancies = new List(); #region 拓展出需要的各个项目的 从business table表? var bsList = SqlSugarHelper.Db.Queryable().ToList(); var projList = SqlSugarHelper.Db.Queryable().ToList(); var subsql = sql.Split(';'); #region 处理 // 正则表达式 string pattern = @"(?i)\balter\s+table\s+[`']?([a-zA-Z0-9_]+)[`']?"; string pattern2 = @"(?i)\bcreate\s+table\s+[`']?([a-zA-Z0-9_]+)[`']?"; int patternType;//1是create,0是update foreach (var sqlstr in subsql) { if (string.IsNullOrWhiteSpace(sqlstr)) { continue; } // 匹配表名 Match match = Regex.Match(sqlstr, pattern); if (string.IsNullOrEmpty(sqlstr)) { continue; } if (match.Length == 0) { match = Regex.Match(sqlstr, pattern2); patternType = 1; } else { patternType = 0; } if (match.Length == 0 ) { //无效的sql LogInfo(baseDirectory + logFileName, $"无法从{SQLFILENAME}文件中解析出正确的create table 或 update table。确保是有效的sql。文本为 {sqlstr}"); continue; } var groupMatch = Regex.Matches(sqlstr, pattern); #endregion if (match.Success) { string tableName = match.Value; Console.WriteLine("Table name: " + tableName); //在Project文件夹下 或者 属于业务表 if (ProjectRelated || bsList.Select(x => x.BusinessTableCode.ToUpper()).Contains(tableName.ToUpper())) { //属于Project相关的表,需要重复执行 foreach (var proj in projList) { // 替换表名为其他表名 string newTableName = tableName + "_" + proj.ProjectIndex; string modifiedSql = ""; if (patternType == 0) { modifiedSql = Regex.Replace(sqlstr, pattern, newTableName); } else { modifiedSql = Regex.Replace(sqlstr, pattern2, newTableName); } Console.WriteLine("Modified SQL: " + modifiedSql); try { SqlSugarHelper.Db.Ado.ExecuteCommand(modifiedSql); } catch (Exception ex) { LogInfo(baseDirectory + logFileName, tableName + " 项目表更新出现问题," + ex.Message); } } } //基础表,执行一次就行 try { SqlSugarHelper.Db.Ado.ExecuteCommand(sql); } catch (Exception ex) { LogInfo(baseDirectory + logFileName, tableName + " 基础表更新出现问题," + ex.Message); } } else { Console.WriteLine("Table name not found."); } } #endregion return discrepancies; } /// /// 比较sql抽取出来的表结构和当前数据库里的 /// /// /// /// public List CompareSchemas(List
sqlSchema, List
dbSchema) { var discrepancies = new List(); #region 拓展出需要的各个项目的 从business table表? var bsList = SqlSugarHelper.Db.Queryable().ToList(); var projList = SqlSugarHelper.Db.Queryable().ToList(); //拓展sqlSchema里的记录,比如ec_enginedata,根据项目拓展到若干个 foreach (var bs in bsList) { var baseTb = sqlSchema.FirstOrDefault(x => x.Name.ToUpper() == bs.BusinessTableCode.ToUpper()); if (baseTb != null) { foreach (var proj in projList) { var projTb = new Table() { Name = baseTb.Name + "_" + proj.ProjectIndex, Columns = baseTb.Columns }; sqlSchema.Add(projTb); } } else { //bus表里有问题 LogInfo(baseDirectory + logFileName, "ec_business_table表中的记录有问题 " + bs.BusinessTableID + " " + bs.BusinessTableCode); } } #endregion foreach (var table in sqlSchema) { if (!dbSchema.Any(x => x.Name.ToUpper() == table.Name.ToUpper())) { discrepancies.Add($"Table {table.Name} is missing in the database."); continue; } var sqlColumns = table.Columns.Select(x => x.ColumnName.ToUpper()).ToList(); var dbColumns = dbSchema.FirstOrDefault(x => x.Name == table.Name).Columns.Select(x => x.ColumnName.ToUpper()).ToList(); // Find missing columns foreach (var column in sqlColumns) { if (!dbColumns.Contains(column)) { discrepancies.Add($"Column {column} is missing in table {table.Name}."); } } } return discrepancies; } /// /// 根据比较结果,生成需要的SQL语句 /// /// /// /// public List GenerateFixSql(List discrepancies, List
sqlSchema) { var sqlStatements = new List(); foreach (var discrepancy in discrepancies) { var parts = discrepancy.Split(' '); if (parts[0] == "Table") { // 生成创建表的SQL语句 string tableName = parts[1]; var columns = sqlSchema.FirstOrDefault(x => x.Name.ToUpper() == tableName.ToUpper())?.Columns; string createTableSql = $"CREATE TABLE {tableName} (\n"; if (columns == null) { LogInfo(baseDirectory + logFileName, "GenerateFixSql时有问题 表匹配不到 " + tableName); continue; } foreach (var column in columns) { if (column.IsNullable) { createTableSql += $"{column.ColumnName} {column.DataType} NULL ,\n"; // 简化的列定义,仅示例 } else { createTableSql += $"{column.ColumnName} {column.DataType} NOT NULL ,\n"; // 简化的列定义,仅示例 } } createTableSql = createTableSql.TrimEnd(',', '\n') + "\n);"; sqlStatements.Add(createTableSql); } else if (parts[0] == "Column") { // 生成添加列的SQL语句 string columnName = parts[1]; string tableName = parts[6].Replace(".", ""); var columns = sqlSchema.FirstOrDefault(x => x.Name.ToUpper() == tableName.ToUpper())?.Columns; if (columns == null) { LogInfo(baseDirectory + logFileName, "GenerateFixSql时有问题 表匹配不到 " + tableName); continue; } var column = columns.FirstOrDefault(Y => Y.ColumnName.ToUpper() == columnName.ToUpper()); if (column == null) { LogInfo(baseDirectory + logFileName, "GenerateFixSql时有问题 列匹配不到 " + tableName + "." + columnName); continue; } //sqlStatements.Add($"ALTER TABLE {tableName} ADD COLUMN {columnName} TEXT;"); if (column.IsNullable) { sqlStatements.Add($"ALTER TABLE {tableName} ADD COLUMN {columnName} {column.DataType} NULL;"); } else { sqlStatements.Add($"ALTER TABLE {tableName} ADD COLUMN {columnName} {column.DataType} NOT NULL;"); } } } return sqlStatements; } /// /// 执行SQL /// /// /// public async Task ExecuteComparisonSQL(List SQLs) { foreach (var SQL in SQLs) { try { await UpdateText(SQL); SqlSugarHelper.Db.Ado.ExecuteCommand(SQL); } catch (MySqlException ex) { LogError(baseDirectory + logFileName, ex, SQL); } } } /// /// 打开日志 /// /// /// private void Button_Click_2(object sender, RoutedEventArgs e) { //打开log try { ProcessStartInfo startInfo = new ProcessStartInfo() { FileName = "notepad.exe", // 指定使用记事本打开 Verb = "runas", // 使用管理员权限启动 Arguments = baseDirectory + logFileName, UseShellExecute = true // 使用操作系统的Shell来执行 }; Process.Start(startInfo); } catch (Exception ex) { MessageBox.Show("无法打开日志文件: " + ex.Message); } } } public class Table { public string Name { get; set; } public List Columns { get; set; } } public class ColumnInfo { public string ColumnName { get; set; } public string DataType { get; set; } public bool IsNullable { get; set; } } }