專案

一般

配置概況

Feature #2643 » App.xaml.cs

莊 施嶔, 03/06/2025 08:53

 
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
{
/// <summary>
/// App.xaml 的互動邏輯
/// </summary>
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"; // 設定最小相容版本號,非常重要,資料結構有變動時一定要來更新

/// <summary>
/// 獲取系統最小必需版本號
/// </summary>
/// <returns>最小必需版本號</returns>
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;
}

/// <summary>
/// 從 SQLServer.cfg.xml 載入資料庫連線字串
/// </summary>
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}");
}
}
}

/// <summary>
/// 記錄日誌訊息
/// </summary>
/// <param name="message">日誌訊息內容</param>
/// <param name="isError">是否為錯誤訊息</param>
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);

}
}

/// <summary>
/// 顯示主視窗
/// </summary>
private void ShowMainWindow()
{
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
}

/// <summary>
/// 顯示系統設定視窗
/// </summary>
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<string>();
var messagesEng = new List<string>();

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;
}

/// <summary>
/// 僅更新資料庫版本號,不進行資料庫結構更新
/// </summary>
/// <param name="newVersion">要更新的新版本號</param>
/// <returns>更新是否成功</returns>
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;
}
}
}
}
(31-31/32)