009_DI-Elec/SWSDBSchemeUpgradeTool/MainWindow.xaml.cs

822 lines
30 KiB
C#
Raw Normal View History

2025-08-13 11:14:39 +08:00
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
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
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 = "";
/// <summary>
/// 是否被初始化过。没有的话就要去执行sws.sql。
/// </summary>
private bool bInitialed;
/// <summary>
/// (iis文件夹根目录
/// </summary>
private string IISDirectory;
/// <summary>
/// exe的目录会带一个\在后面
/// </summary>
private string baseDirectory;
string logFileName = "sync_errors.log";
string Initial_Data_sql = "Initial_Data.sql";
public MainWindow()
{
InitializeComponent();
ContentGrid.DataContext = this;
ReadDBConfig();
ConnDB();
IfInitialed();
}
/// <summary>
/// 读取database.config文件
/// </summary>
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();
}
}
/// <summary>
/// 连接到数据库
/// </summary>
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);
}
2025-08-27 15:40:47 +08:00
/// <summary>
/// 增补更新。去找SQL文件夹下每个小sql的内容
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
2025-08-13 11:14:39 +08:00
private void Button_Click(object sender, RoutedEventArgs e)
{
if (bInitialed)
{
2025-08-27 15:40:47 +08:00
if (!Directory.Exists(IISDirectory + "/" + "SQL"))
{
MessageBox.Show($"IIS根目录({IISDirectory})下的SQL文件夹不存在。请检查。");
return;
}
2025-08-13 11:14:39 +08:00
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);
}
/// <summary>
/// 解析、抽取初始脚本的SQL
/// </summary>
/// <returns></returns>
List<Table> ExtractTables()
{
string sqlFilePath = IISDirectory + "/" + "SQL" + "/" + Initial_Data_sql; // Update with the correct file path
string sqlContent = File.ReadAllText(sqlFilePath);
var tables = new List<Table>();
// Match each CREATE TABLE statement
string tablePattern = @"CREATE\s+TABLE\s+`?(?<TableName>\w+)`?\s*\((?<Columns>[\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;
}
/// <summary>
/// 解析、抽取初始脚本的SQL表中的列
/// </summary>
/// <param name="columnsDefinition"></param>
/// <returns></returns>
public static List<ColumnInfo> ExtractColumns(string columnsDefinition)
{
var columns = new List<ColumnInfo>();
//string columnPattern = @"`(?<ColumnName>\w+)`\s+(?<DataType>\w+\(\d+\))\s+(CHARACTER\s+SET\s+\w+\s+COLLATE\s+\w+\s+)?(?<Nullability>NOT\s+NULL|NULL)";
string columnPattern = @"`(?<ColumnName>\w+)`\s+(?<DataType>\w+(\(\d+\))?)(\s+CHARACTER\s+SET\s+\w+\s+COLLATE\s+\w+)?\s+(?<Nullability>NOT\s+NULL|NULL)";
// Match each column definition inside the CREATE TABLE statement
// string columnPattern = @"`(?<column>\w+)`\s+\w+";// @"`(?<ColumnName>\w+)`\s+(?<ColumnType>[^,]+),?";
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;
}
/// <summary>
/// 读取当前数据库里的表结构
/// </summary>
/// <returns></returns>
public List<Table> GetDatabaseTableStructures()
{
var tableStructures = new List<Table>();
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<string>();
//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<ColumnInfo>();
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;
}
/// <summary>
/// 比较sql抽取出来的表结构和当前数据库里的增补sql小文件
/// </summary>
/// <param name="sqlSchema"></param>
/// <param name="dbSchema"></param>
/// <returns></returns>
public List<string> CompareSchemas2(string sql, bool ProjectRelated,string SQLFILENAME)
{
var discrepancies = new List<string>();
#region business table表
var bsList = SqlSugarHelper.Db.Queryable<ec_business_tableEntity>().ToList();
var projList = SqlSugarHelper.Db.Queryable<ec_projectEntity>().ToList();
var subsql = sql.Split(';');
#region
// 正则表达式
string pattern = @"(?<=ALTER\s+TABLE\s+`)[^`]+";
string pattern2 = @"(?<=CREATE\s+TABLE\s+`)[^`]+";
int patternType;//1是create0是update
foreach (var sqlstr in subsql)
{
if (string.IsNullOrWhiteSpace(sqlstr))
{
continue;
}
2025-08-13 11:14:39 +08:00
// 匹配表名
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}.sql文件中解析出正确的create table 或 update table。确保是有效的sql。文本为 {sqlstr}");
2025-08-13 11:14:39 +08:00
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;
}
/// <summary>
/// 比较sql抽取出来的表结构和当前数据库里的
/// </summary>
/// <param name="sqlSchema"></param>
/// <param name="dbSchema"></param>
/// <returns></returns>
public List<string> CompareSchemas(List<Table> sqlSchema, List<Table> dbSchema)
{
var discrepancies = new List<string>();
#region business table表
var bsList = SqlSugarHelper.Db.Queryable<ec_business_tableEntity>().ToList();
var projList = SqlSugarHelper.Db.Queryable<ec_projectEntity>().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;
}
/// <summary>
/// 根据比较结果生成需要的SQL语句
/// </summary>
/// <param name="discrepancies"></param>
/// <param name="sqlSchema"></param>
/// <returns></returns>
public List<string> GenerateFixSql(List<string> discrepancies, List<Table> sqlSchema)
{
var sqlStatements = new List<string>();
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;
}
/// <summary>
/// 执行SQL
/// </summary>
/// <param name="SQLs"></param>
/// <returns></returns>
public async Task ExecuteComparisonSQL(List<string> SQLs)
{
foreach (var SQL in SQLs)
{
try
{
await UpdateText(SQL);
SqlSugarHelper.Db.Ado.ExecuteCommand(SQL);
}
catch (MySqlException ex)
{
LogError(baseDirectory + logFileName, ex, SQL);
}
}
}
/// <summary>
/// 打开日志
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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<ColumnInfo> Columns { get; set; }
}
public class ColumnInfo
{
public string ColumnName { get; set; }
public string DataType { get; set; }
public bool IsNullable { get; set; }
}
}