009_DI-Elec/SWSDBSchemeUpgradeTool/MainWindow.xaml.cs
2025-08-27 15:40:47 +08:00

818 lines
30 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
/// <summary>
/// 增补更新。去找SQL文件夹下每个小sql的内容
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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);
}
/// <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)
{
// 匹配表名
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。");
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; }
}
}