using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Data.SqlClient; using System.Reflection; using NLog; // 添加 NLog 引用 using System.Xml.Linq; // 用於讀取 XML 配置 namespace BCWPF { /// /// App.xaml 的互動邏輯 /// public partial class App : Application { private readonly string logFilePath = @"C:\MPC_Log\SystemError.log"; private static readonly Logger _logger = LogManager.GetLogger("DbSettings"); private string _masterConnectionString = string.Empty; private string _connectionString = string.Empty; private string _configPath = string.Empty; // 儲存啟動參數 public string[] StartupArgs { get; private set; } // 最小必需版本號,當資料庫版本小於此版本時,需要完整的資料庫更新 // 當資料庫版本大於等於此版本時,只需要更新版本號記錄 //frm_DbSetting.xaml.cs的CreateTables()裡的SQL即為變動Schema的地方 private readonly string _minimumRequiredVersion = "4.2.15.0"; // 設定最小相容版本號,非常重要,資料結構有變動時一定要來更新 /// /// 獲取系統最小必需版本號 /// /// 最小必需版本號 public string GetMinimumRequiredVersion() { return _minimumRequiredVersion; } public App() { LogMessage("App 初始化", false); // 設定配置檔案路徑 string baseDir = AppDomain.CurrentDomain.BaseDirectory; string configDir = Path.Combine(baseDir, "Config"); // 如果是 Debug 模式,使用專案目錄下的 Config if (System.Diagnostics.Debugger.IsAttached) { // 取得專案根目錄(向上三層到專案根目錄) string projectRoot = Path.GetFullPath(Path.Combine(baseDir, @"..\..\..\")); configDir = Path.Combine(projectRoot, "BCWPF", "Config"); LogMessage($"Debug 模式:使用專案目錄 {configDir}", false); } _configPath = Path.Combine(configDir, "SQLServer.cfg.xml"); LoadConnectionStrings(); // 載入連線字串 this.DispatcherUnhandledException += App_DispatcherUnhandledException; this.Startup += App_Startup; } /// /// 從 SQLServer.cfg.xml 載入資料庫連線字串 /// private void LoadConnectionStrings() { try { LogMessage($"開始載入連線字串:{_configPath}", false); if (!File.Exists(_configPath)) { LogMessage($"連線字串配置檔案不存在:{_configPath},使用預設連線字串", true); // 使用預設連線字串 _connectionString = "Data Source=localhost;Initial Catalog=BC_Dev;User ID=sa;Password=p@ssw0rd;"; _masterConnectionString = "Data Source=localhost;Initial Catalog=master;User ID=sa;Password=p@ssw0rd;"; return; } // 讀取 XML 配置文件 XDocument doc = XDocument.Load(_configPath); // 取得命名空間 XNamespace ns = doc.Root.GetDefaultNamespace(); // 尋找 session-factory 元素 var sessionFactory = doc.Root.Element(ns + "session-factory"); if (sessionFactory == null) { LogMessage("配置檔中找不到 session-factory 元素,使用預設連線字串", true); // 使用預設連線字串 _connectionString = "Data Source=localhost;Initial Catalog=BC_Dev;User ID=sa;Password=p@ssw0rd;"; _masterConnectionString = "Data Source=localhost;Initial Catalog=master;User ID=sa;Password=p@ssw0rd;"; return; } // 尋找 connection.connection_string 屬性 var propertyElement = sessionFactory.Elements(ns + "property") .FirstOrDefault(p => p.Attribute("name")?.Value == "connection.connection_string"); if (propertyElement == null) { LogMessage("配置檔中找不到 connection.connection_string 屬性,使用預設連線字串", true); // 使用預設連線字串 _connectionString = "Data Source=localhost;Initial Catalog=BC_Dev;User ID=sa;Password=p@ssw0rd;"; _masterConnectionString = "Data Source=localhost;Initial Catalog=master;User ID=sa;Password=p@ssw0rd;"; return; } // 取得連線字串 string connString = propertyElement.Value.Trim(); if (string.IsNullOrEmpty(connString)) { LogMessage("連線字串為空,使用預設連線字串", true); // 使用預設連線字串 _connectionString = "Data Source=localhost;Initial Catalog=BC_Dev;User ID=sa;Password=p@ssw0rd;"; _masterConnectionString = "Data Source=localhost;Initial Catalog=master;User ID=sa;Password=p@ssw0rd;"; return; } // 解析連線字串 var builder = new SqlConnectionStringBuilder(connString); // 提取出 BC_Dev 的連線字串 _connectionString = connString; // 提取出 master 的連線字串 var masterBuilder = new SqlConnectionStringBuilder(connString) { InitialCatalog = "master" }; _masterConnectionString = masterBuilder.ConnectionString; LogMessage("連線字串載入成功:", false); LogMessage($"BC_Dev 連線字串: {_connectionString}", false); LogMessage($"Master 連線字串: {_masterConnectionString}", false); } catch (Exception ex) { LogMessage($"載入連線字串時發生錯誤: {ex.Message}", true); // 使用預設連線字串 _connectionString = "Data Source=localhost;Initial Catalog=BC_Dev;User ID=sa;Password=p@ssw0rd;"; _masterConnectionString = "Data Source=localhost;Initial Catalog=master;User ID=sa;Password=p@ssw0rd;"; } } private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) { try { string directory = Path.GetDirectoryName(logFilePath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); // 創建目錄 } // 錯誤訊息內容 string errorMessage = $"[{DateTime.Now}] Unhandled Exception:\n{e.Exception}\n{e.Exception.StackTrace}\n"; // 將錯誤訊息寫入檔案 File.AppendAllText(logFilePath, errorMessage); // 阻止應用程式自動關閉(如果需要) e.Handled = true; } catch (Exception ex) { // 如果記錄錯誤時發生錯誤,僅在除錯模式下顯示 if (System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debug.WriteLine($"記錄錯誤時發生異常: {ex.Message}"); } } } /// /// 記錄日誌訊息 /// /// 日誌訊息內容 /// 是否為錯誤訊息 private void LogMessage(string message, bool isError = false) { try { // 使用 NLog 輸出到檔案 if (isError) { _logger.Error(message); } else { _logger.Info(message); } } catch (Exception ex) { // 防止日誌記錄失敗導致應用程式崩潰 System.Diagnostics.Debug.WriteLine($"寫入日誌時發生錯誤: {ex.Message}"); } } private void App_Startup(object sender, StartupEventArgs e) { // 儲存啟動參數 StartupArgs = e.Args; LogMessage($"應用程式啟動參數: {string.Join(", ", e.Args)}", false); try { LogMessage($"系統啟動", false); // 檢查資料庫是否存在 if (!CheckDatabaseExists()) { LogMessage($"資料庫不存在,請先建立資料庫", false); MessageBox.Show("資料庫不存在,請先建立資料庫。\n\nDatabase does not exist. Please create the database first.", "系統警告 / System Warning", MessageBoxButton.OK, MessageBoxImage.Warning); ShowDbSettingWindow(); return; } else { // 檢查資料庫版本 string dbVersion = GetDatabaseVersion(); string currentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); // 如果版本相同,直接啟動應用程式 if (dbVersion == currentVersion) { // 資料庫版本與程式版本一致,直接啟動主視窗 LogMessage($"資料庫版本與程式版本一致,直接啟動主視窗", false); ShowMainWindow(); return; } // 比較版本 Version dbVer; Version currentVer; Version minRequiredVer; // 嘗試解析版本號,如果解析失敗,使用預設值 try { dbVer = new Version(dbVersion); currentVer = new Version(currentVersion); minRequiredVer = new Version(_minimumRequiredVersion); } catch (Exception ex) { // 版本號解析失敗,記錄錯誤 LogMessage($"解析版本號失敗: {ex.Message}", true); MessageBox.Show( $"版本號解析失敗,請確保資料庫和程式使用有效的版本號格式。\n\n" + $"Database and program versions must be in a valid format.\n\n" + $"Error message: {ex.Message}", "版本錯誤", MessageBoxButton.OK, MessageBoxImage.Error); ShowDbSettingWindow(); return; } // 如果資料庫版本大於當前程式版本,提醒使用者可能使用了較舊的程式版本 if (dbVer > currentVer) { LogMessage($"資料庫版本 ({dbVersion}) 高於程式版本 ({currentVersion})", false); MessageBox.Show( $"警告:資料庫版本 ({dbVersion}) 高於程式版本 ({currentVersion}),\n" + $"這可能表示您使用了較舊版本的程式。\n\n" + $"建議選擇以下處理方式:\n" + $"1. 更新程式至最新版本 ({dbVersion} 或更高)\n" + $"2. 使用「更新資料庫」功能來進行降版,系統會自動處理版本差異\n" + $" 且會盡可能保留原有資料庫記錄\n\n" + $"註:更新資料庫功能會根據欄位存在與否自動處理資料遷移,\n" + $"可同時支援升版及降版的情境。\n\n" + $"Warning: Database version ({dbVersion}) is higher than program version ({currentVersion}),\n" + $"This may indicate you are using an older version of the program.\n\n" + $"Suggested actions:\n" + $"1. Update program to latest version ({dbVersion} or higher)\n" + $"2. Use 'Update Database' function to downgrade, system will handle version differences\n" + $" automatically and preserve existing database records when possible\n\n" + $"Note: Database update function will automatically handle data migration based on\n" + $"field existence, supporting both upgrade and downgrade scenarios.", "版本不匹配 / Version Mismatch", MessageBoxButton.OK, MessageBoxImage.Warning); ShowDbSettingWindow(); return; } // 如果資料庫版本大於等於最小必需版本,且資料庫版本小於當前程式版本,只需更新版本號 else if (dbVer >= minRequiredVer && dbVer < currentVer) { // 先提示用戶資料庫版本雖小於系統版本但符合最小相容版本要求 LogMessage($"資料庫版本 ({dbVersion}) 小於程式版本 ({currentVersion}),符合最小相容版本要求", false); MessageBox.Show( $"資料庫版本 ({dbVersion}) 小於程式版本 ({currentVersion}),\n" + $"但已符合最小相容版本 ({_minimumRequiredVersion}) 的要求。\n\n" + $"系統將只更新版本號碼,無需執行完整的資料庫更新。\n\n" + $"Database version ({dbVersion}) is lower than program version ({currentVersion}),\n" + $"but meets the minimum compatible version ({_minimumRequiredVersion}) requirement.\n\n" + $"The system will only update the version number, without executing a complete database update.", "版本資訊 / Version Information", MessageBoxButton.OK, MessageBoxImage.Information); // 直接更新資料庫版本號 if (UpdateDatabaseVersionOnly(currentVersion)) { // 成功更新版本號,可以繼續啟動應用程式 LogMessage($"資料庫版本已從 {dbVersion} 更新至 {currentVersion}", false); MessageBox.Show( $"資料庫版本已從 {dbVersion} 更新至 {currentVersion}。\n\n" + $"Database version has been updated from {dbVersion} to {currentVersion}.\n\n" + $"The system will only update the version number, without executing a complete database update.", "更新成功 / Update Successful", MessageBoxButton.OK, MessageBoxImage.Information); // 版本已更新,繼續啟動主視窗 ShowMainWindow(); return; } else { // 更新版本號失敗,顯示系統設定畫面 LogMessage($"更新資料庫版本號失敗,請檢查資料庫連線設定", false); MessageBox.Show( $"更新資料庫版本號失敗,請檢查資料庫連線設定。\n\n" + $"Database version update failed. Please check the database connection settings.", "更新失敗 / Update Failed", MessageBoxButton.OK, MessageBoxImage.Warning); ShowDbSettingWindow(); return; } } // 資料庫版本低於最小必需版本,需要完整更新 else if (dbVer < minRequiredVer) { // 進行額外的資料庫檢查 var checkResult = PerformDatabaseChecks(); if (checkResult.NeedsManualCheck) { LogMessage($"資料庫版本 ({dbVersion}) 低於最小必需版本 ({_minimumRequiredVersion}),且發現問題:{checkResult.Message}", false); MessageBox.Show( $"資料庫版本 ({dbVersion}) 低於最小必需版本 ({_minimumRequiredVersion}),且發現以下問題:\n\n" + $"{checkResult.Message}\n\n" + "建議:\n" + "1. 請先確認原資料庫的完整性\n" + "2. 如果原資料庫有問題,建議執行新建資料庫並選擇備份原資料庫\n" + "3. 如果原資料庫正常,可以進行資料庫更新\n\n" + "註:更新資料庫功能會根據欄位存在與否自動處理資料遷移,\n" + "可同時支援升版及降版的情境,系統將盡可能保留現有資料。\n\n" + $"Database version ({dbVersion}) is lower than minimum required version ({_minimumRequiredVersion}), and the following issues were found:\n\n" + $"{checkResult.MessageEng}\n\n" + "Suggestions:\n" + "1. Please verify the integrity of the original database first\n" + "2. If the original database has issues, it is recommended to create a new database and backup the original one\n" + "3. If the original database is normal, you can proceed with the database update\n\n" + "Note: The database update function will automatically handle data migration based on field existence,\n" + "supporting both upgrade and downgrade scenarios while preserving existing data whenever possible.", "版本過舊 / Version Too Old", MessageBoxButton.OK, MessageBoxImage.Warning); } else { LogMessage($"資料庫版本 ({dbVersion}) 低於最小必需版本 ({_minimumRequiredVersion}),需要進行完整的資料庫更新", false); MessageBox.Show( $"資料庫版本 ({dbVersion}) 低於最小必需版本 ({_minimumRequiredVersion})。\n\n" + "需要進行完整的資料庫更新。\n\n" + "註:更新資料庫功能會根據欄位存在與否自動處理資料遷移,\n" + "可同時支援升版及降版的情境,系統將盡可能保留現有資料。\n\n" + $"Database version ({dbVersion}) is lower than minimum required version ({_minimumRequiredVersion}).\n\n" + "A complete database update is required.\n\n" + "Note: The database update function will automatically handle data migration based on field existence,\n" + "supporting both upgrade and downgrade scenarios while preserving existing data whenever possible.", "版本過舊 / Version Too Old", MessageBoxButton.OK, MessageBoxImage.Warning); } // 無論如何都顯示系統設定視窗 ShowDbSettingWindow(); return; } } // 如果資料庫檢查都通過,則顯示主視窗 ShowMainWindow(); } catch (Exception ex) { // 使用 NLog 記錄錯誤 LogMessage($"檢查資料庫時發生錯誤:{ex.Message}", true); MessageBox.Show( $"檢查資料庫時發生錯誤:{ex.Message}\n\nError occurred while checking database: {ex.Message}", "系統錯誤 / System Error", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// 顯示主視窗 /// private void ShowMainWindow() { MainWindow mainWindow = new MainWindow(); mainWindow.Show(); } /// /// 顯示系統設定視窗 /// private void ShowDbSettingWindow() { LogMessage($"顯示系統設定視窗", false); frm_DbSetting systemSetting = new frm_DbSetting(); systemSetting.Show(); } private string GetDatabaseVersion() { try { using (var connection = new SqlConnection(_connectionString)) { connection.Open(); using (var command = new SqlCommand("SELECT version FROM Version", connection)) { var result = command.ExecuteScalar(); return result?.ToString() ?? "0.0.0.0"; } } } catch (Exception ex) { // 使用 NLog 記錄錯誤 LogMessage($"檢查資料庫版本時發生錯誤: {ex.Message}", true); return "0.0.0.0"; // 發生錯誤時返回預設版本 } } private bool CheckDatabaseExists() { try { using (var connection = new SqlConnection(_masterConnectionString)) { connection.Open(); using (var command = new SqlCommand("SELECT database_id FROM sys.databases WHERE Name = 'BC_Dev'", connection)) { var result = command.ExecuteScalar(); return result != null; } } } catch (Exception ex) { // 使用 NLog 記錄錯誤 LogMessage($"檢查資料庫時發生錯誤: {ex.Message}", true); throw; } } private class DatabaseCheckResult { public bool NeedsManualCheck { get; set; } public string Message { get; set; } public string MessageEng { get; set; } } private DatabaseCheckResult PerformDatabaseChecks() { var result = new DatabaseCheckResult { NeedsManualCheck = false, Message = string.Empty }; var messages = new List(); var messagesEng = new List(); try { using (var connection = new SqlConnection(_connectionString)) { connection.Open(); // 檢查資料表數量 using (var command = new SqlCommand( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", connection)) { int tableCount = Convert.ToInt32(command.ExecuteScalar()); if (tableCount < 40) { result.NeedsManualCheck = true; messages.Add($"資料表數量 ({tableCount}) 小於預期值 (40),資料庫可能不完整"); messagesEng.Add($"The number of tables ({tableCount}) is less than expected (40); the database may be incomplete."); } } // 檢查資料庫建立日期 using (var command = new SqlCommand( "SELECT create_date FROM sys.databases WHERE name = 'BC_Dev'", connection)) { DateTime createDate = Convert.ToDateTime(command.ExecuteScalar()); if ((DateTime.Now - createDate).TotalDays < 1) { result.NeedsManualCheck = true; messages.Add($"資料庫建立時間 ({createDate}) 距今少於1天"); messagesEng.Add($"The database creation time ({createDate}) is less than one day ago."); } } // 檢查特定資料表是否存在及是否有資料 string[] tablesToCheck = { "HAPJID", "HASHEET", "HACASSETTE", "AECDATAMAP", "UASUSR", "UASUFNC", "UASUSRGRP" }; foreach (var table in tablesToCheck) { // 先檢查資料表是否存在 using (var command = new SqlCommand( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @TableName", connection)) { command.Parameters.AddWithValue("@TableName", table); int tableExists = Convert.ToInt32(command.ExecuteScalar()); if (tableExists == 0) { result.NeedsManualCheck = true; messages.Add($"資料表 {table} 不存在"); messagesEng.Add($"The table {table} does not exist."); continue; // 如果資料表不存在,跳過檢查資料 } } // 檢查資料表是否有資料 using (var command = new SqlCommand($"SELECT COUNT(*) FROM {table}", connection)) { int count = Convert.ToInt32(command.ExecuteScalar()); if (count == 0) { result.NeedsManualCheck = true; messages.Add($"資料表 {table} 中沒有資料"); messagesEng.Add($"There is no data in the table {table}."); } } } } if (messages.Any()) { result.Message = string.Join("\n", messages); } if (messagesEng.Any()) { result.MessageEng = string.Join("\n", messagesEng); } } catch (Exception ex) { // 使用 NLog 記錄錯誤 LogMessage($"執行資料庫檢查時發生錯誤: {ex.Message}", true); result.NeedsManualCheck = true; result.Message = "執行資料庫檢查時發生錯誤,請聯繫系統管理員"; } return result; } /// /// 僅更新資料庫版本號,不進行資料庫結構更新 /// /// 要更新的新版本號 /// 更新是否成功 private bool UpdateDatabaseVersionOnly(string newVersion) { try { LogMessage($"僅更新資料庫版本號,不進行資料庫結構更新", false); using (var connection = new SqlConnection(_connectionString)) { connection.Open(); using (var command = new SqlCommand("UPDATE Version SET version = @NewVersion", connection)) { command.Parameters.AddWithValue("@NewVersion", newVersion); int affectedRows = command.ExecuteNonQuery(); return affectedRows > 0; } } } catch (Exception ex) { // 使用 NLog 記錄錯誤 LogMessage($"更新資料庫版本號時發生錯誤: {ex.Message}", true); return false; } } } }