1090 lines
42 KiB
C#
1090 lines
42 KiB
C#
using Microsoft.Data.Sqlite;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Windows.Forms;
|
||
|
||
namespace 報到系統客戶端.Data
|
||
{
|
||
/// <summary>
|
||
/// SQLite 資料庫服務初始化和連線管理
|
||
/// </summary>
|
||
public class DatabaseService
|
||
{
|
||
private static DatabaseService _instance;
|
||
private static readonly object _lockObject = new object();
|
||
|
||
private string _databasePath;
|
||
private SqliteConnection _connection;
|
||
private const string DB_FILENAME = "data.db";
|
||
|
||
// 靜態資料表定義
|
||
private static readonly Dictionary<string, List<TableColumnDefinition>> TableDefinitions =
|
||
new Dictionary<string, List<TableColumnDefinition>>
|
||
{
|
||
{
|
||
"Classes", new List<TableColumnDefinition>
|
||
{
|
||
new TableColumnDefinition { Name = "ID", Type = "INTEGER PRIMARY KEY", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CourseCode", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CourseName", Type = "TEXT", IsNotNull = true, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CourseDate", Type = "DATETIME", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CourseLocation", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "NotificationTemplate", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "AgreedTerms", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "StartTime", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "EndTime", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CreatedAt", Type = "DATETIME", IsNotNull = false, DefaultValue = "CURRENT_TIMESTAMP" },
|
||
new TableColumnDefinition { Name = "UpdatedAt", Type = "DATETIME", IsNotNull = false, DefaultValue = "CURRENT_TIMESTAMP" }
|
||
}
|
||
},
|
||
{
|
||
"SignUps", new List<TableColumnDefinition>
|
||
{
|
||
new TableColumnDefinition { Name = "ID", Type = "INTEGER PRIMARY KEY AUTOINCREMENT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CourseID", Type = "INTEGER", IsNotNull = true, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "SeqNo", Type = "INTEGER", IsNotNull = true, DefaultValue = "0" },
|
||
new TableColumnDefinition { Name = "Name", Type = "TEXT", IsNotNull = true, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "Mobile", Type = "TEXT", IsNotNull = true, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "QRCode", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "SignUpType", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "IDNumber", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "IDType", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "MealType", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "Email", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "LossJobTimes", Type = "INTEGER", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "Gender", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "AgreementStatus", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CheckInTime", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CheckOutTime", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CreatedAt", Type = "DATETIME", IsNotNull = false, DefaultValue = "CURRENT_TIMESTAMP" },
|
||
new TableColumnDefinition { Name = "UpdatedAt", Type = "DATETIME", IsNotNull = false, DefaultValue = "CURRENT_TIMESTAMP" }
|
||
}
|
||
},
|
||
{
|
||
"BPSN", new List<TableColumnDefinition>
|
||
{
|
||
new TableColumnDefinition { Name = "BPENO", Type = "TEXT PRIMARY KEY", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "BPNME", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "BPAWD", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "Is系統管理群組", Type = "BOOLEAN", IsNotNull = false, DefaultValue = "0" },
|
||
new TableColumnDefinition { Name = "Is客戶管理群組", Type = "BOOLEAN", IsNotNull = false, DefaultValue = "0" },
|
||
new TableColumnDefinition { Name = "Is現場工作群組", Type = "BOOLEAN", IsNotNull = false, DefaultValue = "0" },
|
||
new TableColumnDefinition { Name = "Is查詢群組", Type = "BOOLEAN", IsNotNull = false, DefaultValue = "0" },
|
||
new TableColumnDefinition { Name = "CreatedAt", Type = "DATETIME", IsNotNull = false, DefaultValue = "CURRENT_TIMESTAMP" },
|
||
new TableColumnDefinition { Name = "UpdatedAt", Type = "DATETIME", IsNotNull = false, DefaultValue = "CURRENT_TIMESTAMP" }
|
||
}
|
||
},
|
||
{
|
||
"CheckIn", new List<TableColumnDefinition>
|
||
{
|
||
new TableColumnDefinition { Name = "ID", Type = "INTEGER PRIMARY KEY AUTOINCREMENT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CourseID", Type = "INTEGER", IsNotNull = true, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "Name", Type = "TEXT", IsNotNull = true, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "Mobile", Type = "TEXT", IsNotNull = true, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "SignUpType", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "IDNumber", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "IDType", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "MealType", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "Email", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "LossJobTimes", Type = "INTEGER", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "Gender", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "AgreementStatus", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CheckInType", Type = "TEXT", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CheckInTime", Type = "DATETIME", IsNotNull = true, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "UploadTime", Type = "DATETIME", IsNotNull = false, DefaultValue = null },
|
||
new TableColumnDefinition { Name = "CreatedAt", Type = "DATETIME", IsNotNull = false, DefaultValue = "CURRENT_TIMESTAMP" },
|
||
new TableColumnDefinition { Name = "UpdatedAt", Type = "DATETIME", IsNotNull = false, DefaultValue = "CURRENT_TIMESTAMP" }
|
||
}
|
||
}
|
||
};
|
||
|
||
private static readonly Dictionary<string, string[]> TableIndexes =
|
||
new Dictionary<string, string[]>
|
||
{
|
||
{ "Classes", new[] { "idx_classes_courseid|CourseCode" } },
|
||
{ "SignUps", new[] {
|
||
"idx_signups_courseid|CourseID",
|
||
"idx_signups_seqno|SeqNo",
|
||
"idx_signups_qrcode|QRCode",
|
||
"idx_signups_name|Name",
|
||
"idx_signups_mobile|Mobile",
|
||
"idx_signups_idnumber|IDNumber",
|
||
"idx_signups_email|Email"
|
||
}},
|
||
{ "BPSN", new[] { "idx_bpsn_bpeno|BPENO" } },
|
||
{ "CheckIn", new[] {
|
||
"idx_checkin_courseid|CourseID",
|
||
"idx_checkin_name|Name",
|
||
"idx_checkin_mobile|Mobile",
|
||
"idx_checkin_idnumber|IDNumber",
|
||
"idx_checkin_email|Email",
|
||
"idx_checkin_checkintime|CheckInTime"
|
||
}}
|
||
};
|
||
|
||
/// <summary>
|
||
/// 取得單一實例
|
||
/// </summary>
|
||
public static DatabaseService Instance
|
||
{
|
||
get
|
||
{
|
||
if (_instance == null)
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
if (_instance == null)
|
||
{
|
||
_instance = new DatabaseService();
|
||
}
|
||
}
|
||
}
|
||
return _instance;
|
||
}
|
||
}
|
||
|
||
public string DatabasePath
|
||
{
|
||
get { return _databasePath; }
|
||
}
|
||
|
||
private DatabaseService()
|
||
{
|
||
try
|
||
{
|
||
// 設定資料庫路徑 AppData\Roaming\報到系統客戶端
|
||
string appDataPath = Path.Combine(
|
||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||
"報到系統客戶端"
|
||
);
|
||
|
||
// 如果資料夾不存在則建立
|
||
if (!Directory.Exists(appDataPath))
|
||
{
|
||
Directory.CreateDirectory(appDataPath);
|
||
}
|
||
|
||
// 建立 DB 子資料夾
|
||
string dbPath = Path.Combine(appDataPath, "DB");
|
||
if (!Directory.Exists(dbPath))
|
||
{
|
||
Directory.CreateDirectory(dbPath);
|
||
}
|
||
|
||
_databasePath = Path.Combine(dbPath, DB_FILENAME);
|
||
|
||
// 初始化資料庫
|
||
InitializeDatabase();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception($"初始化資料庫服務失敗:{ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取得資料庫連線
|
||
/// </summary>
|
||
public SqliteConnection GetConnection()
|
||
{
|
||
try
|
||
{
|
||
if (_connection == null || _connection.State == System.Data.ConnectionState.Closed)
|
||
{
|
||
// 使用改進的連線字串,設定更多參數確保穩定性
|
||
string connectionString = string.Format(
|
||
"Data Source={0};",
|
||
_databasePath
|
||
);
|
||
|
||
_connection = new SqliteConnection(connectionString);
|
||
_connection.Open();
|
||
}
|
||
return _connection;
|
||
}
|
||
catch (DllNotFoundException ex)
|
||
{
|
||
throw new Exception(
|
||
"無法載入 SQLite 原生庫 (e_sqlite3.dll)。\n\n" +
|
||
"請嘗試以下方案:\n" +
|
||
"1. 確認專案目標平台設定(x86 或 x64)\n" +
|
||
"2. 在 NuGet Package Manager 執行:\n" +
|
||
" - Update-Package System.Data.SQLite -Reinstall\n" +
|
||
"3. 確認應用程式執行的平台與編譯平台相符\n" +
|
||
"4. 清理並重新編譯專案\n\n" +
|
||
"原始錯誤:" + ex.Message, ex);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception($"開啟資料庫連線失敗:{ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化資料庫(建立表格)
|
||
/// </summary>
|
||
private void InitializeDatabase()
|
||
{
|
||
try
|
||
{
|
||
var conn = GetConnection();
|
||
|
||
// 根據定義動態建立所有表格
|
||
foreach (var tableName in TableDefinitions.Keys)
|
||
{
|
||
CreateTableFromDefinition(conn, tableName);
|
||
CreateIndexesForTable(conn, tableName);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception($"初始化資料庫失敗:{ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根據定義動態建立表格
|
||
/// </summary>
|
||
private void CreateTableFromDefinition(SqliteConnection conn, string tableName)
|
||
{
|
||
try
|
||
{
|
||
if (!TableDefinitions.ContainsKey(tableName))
|
||
{
|
||
throw new Exception($"找不到表格 {tableName} 的定義");
|
||
}
|
||
|
||
string sql = BuildCreateTableSql(tableName);
|
||
|
||
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
|
||
{
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception($"建立表格 {tableName} 失敗:{ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根據定義建立表格索引
|
||
/// </summary>
|
||
private void CreateIndexesForTable(SqliteConnection conn, string tableName)
|
||
{
|
||
try
|
||
{
|
||
if (!TableIndexes.ContainsKey(tableName))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var indexes = TableIndexes[tableName];
|
||
foreach (var indexDef in indexes)
|
||
{
|
||
string[] parts = indexDef.Split('|');
|
||
if (parts.Length == 2)
|
||
{
|
||
string indexName = parts[0];
|
||
string columnName = parts[1];
|
||
string sql = $"CREATE INDEX IF NOT EXISTS {indexName} ON {tableName}({columnName})";
|
||
|
||
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
|
||
{
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"建立表格 {tableName} 索引失敗:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 動態構建 CREATE TABLE SQL 語句
|
||
/// </summary>
|
||
private string BuildCreateTableSql(string tableName)
|
||
{
|
||
if (!TableDefinitions.ContainsKey(tableName))
|
||
{
|
||
throw new Exception($"找不到表格 {tableName} 的定義");
|
||
}
|
||
|
||
var columns = TableDefinitions[tableName];
|
||
var columnSql = new List<string>();
|
||
|
||
foreach (var column in columns)
|
||
{
|
||
string colDef = $"{column.Name} {column.Type}";
|
||
|
||
if (column.IsNotNull)
|
||
{
|
||
colDef += " NOT NULL";
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(column.DefaultValue))
|
||
{
|
||
colDef += $" DEFAULT {column.DefaultValue}";
|
||
}
|
||
|
||
columnSql.Add(colDef);
|
||
}
|
||
|
||
string sql = $"CREATE TABLE IF NOT EXISTS {tableName} (\n " +
|
||
string.Join(",\n ", columnSql) +
|
||
"\n)";
|
||
|
||
return sql;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 動態構建 ALTER TABLE ADD COLUMN SQL 語句
|
||
/// </summary>
|
||
private string BuildAddColumnSql(string tableName, TableColumnDefinition column)
|
||
{
|
||
string colDef = $"{column.Name} {column.Type}";
|
||
|
||
if (column.IsNotNull)
|
||
{
|
||
colDef += " NOT NULL";
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(column.DefaultValue))
|
||
{
|
||
colDef += $" DEFAULT {column.DefaultValue}";
|
||
}
|
||
|
||
return $"ALTER TABLE {tableName} ADD COLUMN {colDef}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除所有表格資料
|
||
/// </summary>
|
||
public void ClearAllTables()
|
||
{
|
||
try
|
||
{
|
||
var conn = GetConnection();
|
||
|
||
// 清除 SignUps 表
|
||
using (SqliteCommand cmd = new SqliteCommand("DELETE FROM SignUps", conn))
|
||
{
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
|
||
// 清除 Classes 表
|
||
using (SqliteCommand cmd = new SqliteCommand("DELETE FROM Classes", conn))
|
||
{
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
|
||
// 清除 BPSN 表
|
||
using (SqliteCommand cmd = new SqliteCommand("DELETE FROM BPSN", conn))
|
||
{
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
|
||
// 清除 CheckIn 表
|
||
using (SqliteCommand cmd = new SqliteCommand("DELETE FROM CheckIn", conn))
|
||
{
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
|
||
// 重設自動遞增序號
|
||
using (SqliteCommand cmd = new SqliteCommand("DELETE FROM sqlite_sequence", conn))
|
||
{
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception($"清除表格失敗:{ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 檢查資料表是否存在
|
||
/// </summary>
|
||
public bool TableExists(string tableName)
|
||
{
|
||
try
|
||
{
|
||
var conn = GetConnection();
|
||
string sql = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=@tableName";
|
||
|
||
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
|
||
{
|
||
cmd.Parameters.AddWithValue("@tableName", tableName);
|
||
Int64 count = (Int64)cmd.ExecuteScalar();
|
||
return count > 0;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception($"檢查表格是否存在失敗:{ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重建所有資料表
|
||
/// </summary>
|
||
public void DropAndCreateAllTables()
|
||
{
|
||
try
|
||
{
|
||
var conn = GetConnection();
|
||
|
||
// 刪除表格順序很重要,需要考慮外鍵關係
|
||
// 從靜態定義中取得表格名稱,並按反向順序刪除
|
||
var tableNames = new List<string>(TableDefinitions.Keys);
|
||
tableNames.Reverse();
|
||
|
||
foreach (string tableName in tableNames)
|
||
{
|
||
string sql = $"DROP TABLE IF EXISTS {tableName}";
|
||
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
|
||
{
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
}
|
||
|
||
// 重新初始化資料庫結構
|
||
InitializeDatabase();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception($"刪除表格失敗:{ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刪除指定的資料表
|
||
/// </summary>
|
||
public void DropTable(string tableName)
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(tableName))
|
||
{
|
||
throw new ArgumentException("表格名稱不能為空", nameof(tableName));
|
||
}
|
||
|
||
var conn = GetConnection();
|
||
string sql = $"DROP TABLE IF EXISTS {tableName}";
|
||
|
||
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
|
||
{
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception($"刪除表格失敗:{ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取得表格中現有的欄位名稱列表
|
||
/// </summary>
|
||
private HashSet<string> GetExistingColumnNames(string tableName)
|
||
{
|
||
var columnNames = new HashSet<string>();
|
||
|
||
try
|
||
{
|
||
var columns = GetTableColumns(tableName);
|
||
foreach (var column in columns)
|
||
{
|
||
columnNames.Add(column.ColumnName);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略錯誤
|
||
}
|
||
|
||
return columnNames;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取資料表的列信息
|
||
/// </summary>
|
||
public List<TableColumnInfo> GetTableColumns(string tableName)
|
||
{
|
||
var columns = new List<TableColumnInfo>();
|
||
|
||
try
|
||
{
|
||
var conn = GetConnection();
|
||
string sql = $"PRAGMA table_info({tableName})";
|
||
|
||
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
|
||
{
|
||
using (SqliteDataReader reader = cmd.ExecuteReader())
|
||
{
|
||
while (reader.Read())
|
||
{
|
||
columns.Add(new TableColumnInfo
|
||
{
|
||
ColumnId = reader.GetInt32(0),
|
||
ColumnName = reader.GetString(1),
|
||
ColumnType = reader.GetString(2),
|
||
IsNotNull = reader.GetInt32(3) != 0,
|
||
DefaultValue = reader.IsDBNull(4) ? null : reader.GetString(4),
|
||
IsPrimaryKey = reader.GetInt32(5) != 0
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception($"獲取表格列信息失敗:{ex.Message}", ex);
|
||
}
|
||
|
||
return columns;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 檢查並補足缺少的欄位
|
||
/// </summary>
|
||
public void AddMissingColumns()
|
||
{
|
||
try
|
||
{
|
||
var conn = GetConnection();
|
||
|
||
foreach (var tableName in TableDefinitions.Keys)
|
||
{
|
||
if (!TableExists(tableName))
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"表格 {tableName} 不存在,跳過欄位檢查");
|
||
continue;
|
||
}
|
||
|
||
var expectedColumns = TableDefinitions[tableName];
|
||
var existingColumnNames = GetExistingColumnNames(tableName);
|
||
|
||
foreach (var column in expectedColumns)
|
||
{
|
||
if (!existingColumnNames.Contains(column.Name))
|
||
{
|
||
try
|
||
{
|
||
string sql = BuildAddColumnSql(tableName, column);
|
||
|
||
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
|
||
{
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
|
||
System.Diagnostics.Debug.WriteLine($"已新增欄位 {tableName}.{column.Name}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"新增欄位 {tableName}.{column.Name} 失敗:{ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception($"檢查並新增缺少的欄位失敗:{ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 驗證所有表格的結構是否符合定義
|
||
/// </summary>
|
||
public bool ValidateAllTablesStructure()
|
||
{
|
||
try
|
||
{
|
||
foreach (var tableName in TableDefinitions.Keys)
|
||
{
|
||
if (!ValidateSingleTableStructure(tableName))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 驗證單一表格的結構是否符合定義
|
||
/// </summary>
|
||
public bool ValidateSingleTableStructure(string tableName)
|
||
{
|
||
try
|
||
{
|
||
if (!TableDefinitions.ContainsKey(tableName))
|
||
{
|
||
throw new Exception($"找不到表格 {tableName} 的定義");
|
||
}
|
||
|
||
if (!TableExists(tableName))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var expectedColumns = TableDefinitions[tableName];
|
||
var existingColumnNames = GetExistingColumnNames(tableName);
|
||
|
||
foreach (var expectedColumn in expectedColumns)
|
||
{
|
||
if (!existingColumnNames.Contains(expectedColumn.Name))
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"表格 {tableName} 缺少欄位:{expectedColumn.Name}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"驗證表格 {tableName} 結構失敗:{ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取所有表格與其定義的結構對比結果
|
||
/// </summary>
|
||
public Dictionary<string, TableStructureValidationResult> ValidateAllTablesStructureDetailed()
|
||
{
|
||
var results = new Dictionary<string, TableStructureValidationResult>();
|
||
|
||
foreach (var tableName in TableDefinitions.Keys)
|
||
{
|
||
results[tableName] = ValidateSingleTableStructureDetailed(tableName);
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取單一表格與定義的結構對比結果(詳細版)
|
||
/// </summary>
|
||
public TableStructureValidationResult ValidateSingleTableStructureDetailed(string tableName)
|
||
{
|
||
var result = new TableStructureValidationResult { TableName = tableName };
|
||
|
||
try
|
||
{
|
||
if (!TableDefinitions.ContainsKey(tableName))
|
||
{
|
||
result.IsValid = false;
|
||
result.Message = $"找不到表格 {tableName} 的定義";
|
||
return result;
|
||
}
|
||
|
||
if (!TableExists(tableName))
|
||
{
|
||
result.IsValid = false;
|
||
result.Message = $"表格 {tableName} 不存在";
|
||
result.MissingColumns = new List<string>(TableDefinitions[tableName].Count);
|
||
foreach (var col in TableDefinitions[tableName])
|
||
{
|
||
result.MissingColumns.Add(col.Name);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
var expectedColumns = TableDefinitions[tableName];
|
||
var existingColumns = GetTableColumns(tableName);
|
||
var existingColumnNames = GetExistingColumnNames(tableName);
|
||
|
||
result.MissingColumns = new List<string>();
|
||
result.ExtraColumns = new List<string>();
|
||
|
||
// 檢查缺少的欄位
|
||
foreach (var expectedColumn in expectedColumns)
|
||
{
|
||
if (!existingColumnNames.Contains(expectedColumn.Name))
|
||
{
|
||
result.MissingColumns.Add(expectedColumn.Name);
|
||
}
|
||
}
|
||
|
||
// 檢查多餘的欄位
|
||
foreach (var existingColumn in existingColumns)
|
||
{
|
||
if (!expectedColumns.Any(c => c.Name == existingColumn.ColumnName))
|
||
{
|
||
result.ExtraColumns.Add(existingColumn.ColumnName);
|
||
}
|
||
}
|
||
|
||
result.IsValid = result.MissingColumns.Count == 0 && result.ExtraColumns.Count == 0;
|
||
|
||
if (result.IsValid)
|
||
{
|
||
result.Message = $"表格 {tableName} 結構驗證成功";
|
||
}
|
||
else
|
||
{
|
||
var issues = new List<string>();
|
||
if (result.MissingColumns.Count > 0)
|
||
{
|
||
issues.Add($"缺少 {result.MissingColumns.Count} 個欄位:{string.Join(", ", result.MissingColumns)}");
|
||
}
|
||
if (result.ExtraColumns.Count > 0)
|
||
{
|
||
issues.Add($"有 {result.ExtraColumns.Count} 個多餘欄位:{string.Join(", ", result.ExtraColumns)}");
|
||
}
|
||
result.Message = string.Join(";", issues);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
result.IsValid = false;
|
||
result.Message = $"驗證表格 {tableName} 結構失敗:{ex.Message}";
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 執行資料庫健康檢查
|
||
/// </summary>
|
||
public DatabaseHealthCheckResult PerformHealthCheck()
|
||
{
|
||
var healthCheckResult = new DatabaseHealthCheckResult();
|
||
healthCheckResult.CheckTime = DateTime.Now;
|
||
|
||
try
|
||
{
|
||
// 檢查所有表格是否存在
|
||
healthCheckResult.AllTablesExist = true;
|
||
foreach (var tableName in TableDefinitions.Keys)
|
||
{
|
||
if (!TableExists(tableName))
|
||
{
|
||
healthCheckResult.AllTablesExist = false;
|
||
if (healthCheckResult.MissingTables == null)
|
||
{
|
||
healthCheckResult.MissingTables = new List<string>();
|
||
}
|
||
healthCheckResult.MissingTables.Add(tableName);
|
||
}
|
||
}
|
||
|
||
// 驗證所有表格結構
|
||
var validationResults = ValidateAllTablesStructureDetailed();
|
||
healthCheckResult.TableValidationResults = validationResults;
|
||
healthCheckResult.AllTablesValid = validationResults.Values.All(r => r.IsValid);
|
||
|
||
// 獲取資料庫統計信息
|
||
healthCheckResult.TablesStatistics = GetAllTablesStatistics();
|
||
|
||
// 獲取資料庫檔案大小
|
||
healthCheckResult.DatabaseFileSize = GetDatabaseFileSize();
|
||
healthCheckResult.DatabaseFileSizeFormatted = GetDatabaseFileSizeFormatted();
|
||
|
||
healthCheckResult.IsHealthy = healthCheckResult.AllTablesExist && healthCheckResult.AllTablesValid;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
healthCheckResult.IsHealthy = false;
|
||
healthCheckResult.ErrorMessage = ex.Message;
|
||
}
|
||
|
||
return healthCheckResult;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自動修復資料庫結構(根據定義補足缺少的欄位)
|
||
/// </summary>
|
||
public DatabaseRepairResult AutoRepairDatabase()
|
||
{
|
||
var repairResult = new DatabaseRepairResult();
|
||
repairResult.RepairTime = DateTime.Now;
|
||
|
||
try
|
||
{
|
||
var conn = GetConnection();
|
||
|
||
foreach (var tableName in TableDefinitions.Keys)
|
||
{
|
||
var tableRepairResult = new TableRepairResult { TableName = tableName };
|
||
|
||
if (!TableExists(tableName))
|
||
{
|
||
// 建立缺少的表格
|
||
try
|
||
{
|
||
CreateTableFromDefinition(conn, tableName);
|
||
CreateIndexesForTable(conn, tableName);
|
||
tableRepairResult.IsRepaired = true;
|
||
tableRepairResult.Message = $"表格 {tableName} 已建立";
|
||
tableRepairResult.Action = "CreateTable";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
tableRepairResult.IsRepaired = false;
|
||
tableRepairResult.Message = $"建立表格 {tableName} 失敗:{ex.Message}";
|
||
tableRepairResult.Action = "CreateTable";
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 補足缺少的欄位
|
||
var expectedColumns = TableDefinitions[tableName];
|
||
var existingColumnNames = GetExistingColumnNames(tableName);
|
||
var addedColumns = new List<string>();
|
||
|
||
foreach (var column in expectedColumns)
|
||
{
|
||
if (!existingColumnNames.Contains(column.Name))
|
||
{
|
||
try
|
||
{
|
||
string sql = BuildAddColumnSql(tableName, column);
|
||
|
||
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
|
||
{
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
|
||
addedColumns.Add(column.Name);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"新增欄位 {tableName}.{column.Name} 失敗:{ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
if (addedColumns.Count > 0)
|
||
{
|
||
tableRepairResult.IsRepaired = true;
|
||
tableRepairResult.Message = $"已新增 {addedColumns.Count} 個欄位:{string.Join(", ", addedColumns)}";
|
||
tableRepairResult.Action = "AddColumns";
|
||
tableRepairResult.AddedColumns = addedColumns;
|
||
}
|
||
else
|
||
{
|
||
tableRepairResult.IsRepaired = true;
|
||
tableRepairResult.Message = $"表格 {tableName} 無需修復";
|
||
tableRepairResult.Action = "NoAction";
|
||
}
|
||
}
|
||
|
||
repairResult.TableRepairResults.Add(tableRepairResult);
|
||
}
|
||
|
||
repairResult.IsSuccessful = repairResult.TableRepairResults.All(r => r.IsRepaired);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
repairResult.IsSuccessful = false;
|
||
repairResult.ErrorMessage = ex.Message;
|
||
}
|
||
|
||
return repairResult;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 關閉資料庫連線
|
||
/// </summary>
|
||
public void CloseConnection()
|
||
{
|
||
try
|
||
{
|
||
if (_connection != null && _connection.State != System.Data.ConnectionState.Closed)
|
||
{
|
||
_connection.Close();
|
||
_connection.Dispose();
|
||
_connection = null;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"關閉連線時發生錯誤:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取所有資料表的統計信息
|
||
/// </summary>
|
||
public List<TableStatistics> GetAllTablesStatistics()
|
||
{
|
||
var statistics = new List<TableStatistics>();
|
||
|
||
foreach (var tableName in TableDefinitions.Keys)
|
||
{
|
||
try
|
||
{
|
||
if (TableExists(tableName))
|
||
{
|
||
var conn = GetConnection();
|
||
string sql = $"SELECT COUNT(*) FROM {tableName}";
|
||
|
||
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
|
||
{
|
||
int recordCount = (int)cmd.ExecuteScalar();
|
||
|
||
statistics.Add(new TableStatistics
|
||
{
|
||
TableName = tableName,
|
||
RecordCount = recordCount,
|
||
CheckTime = DateTime.Now
|
||
});
|
||
}
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 跳過無法取得統計信息的表格
|
||
}
|
||
}
|
||
|
||
return statistics;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取資料庫檔案大小(單位:字節)
|
||
/// </summary>
|
||
public long GetDatabaseFileSize()
|
||
{
|
||
try
|
||
{
|
||
if (File.Exists(_databasePath))
|
||
{
|
||
var fileInfo = new FileInfo(_databasePath);
|
||
return fileInfo.Length;
|
||
}
|
||
return 0;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"獲取資料庫文件大小失敗:{ex.Message}");
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取資料庫檔案大小的格式化字符串(KB、MB等)
|
||
/// </summary>
|
||
public string GetDatabaseFileSizeFormatted()
|
||
{
|
||
long sizeInBytes = GetDatabaseFileSize();
|
||
|
||
string[] sizes = { "B", "KB", "MB", "GB" };
|
||
double len = sizeInBytes;
|
||
int order = 0;
|
||
|
||
while (len >= 1024 && order < sizes.Length - 1)
|
||
{
|
||
order++;
|
||
len = len / 1024;
|
||
}
|
||
|
||
return $"{len:0.##} {sizes[order]}";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 資料表欄位定義
|
||
/// </summary>
|
||
public class TableColumnDefinition
|
||
{
|
||
public string Name { get; set; }
|
||
public string Type { get; set; }
|
||
public bool IsNotNull { get; set; }
|
||
public string DefaultValue { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 資料表列信息
|
||
/// </summary>
|
||
public class TableColumnInfo
|
||
{
|
||
public int ColumnId { get; set; }
|
||
public string ColumnName { get; set; }
|
||
public string ColumnType { get; set; }
|
||
public bool IsNotNull { get; set; }
|
||
public string DefaultValue { get; set; }
|
||
public bool IsPrimaryKey { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 資料表統計信息
|
||
/// </summary>
|
||
public class TableStatistics
|
||
{
|
||
public string TableName { get; set; }
|
||
public int RecordCount { get; set; }
|
||
public DateTime CheckTime { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 表格結構驗證結果
|
||
/// </summary>
|
||
public class TableStructureValidationResult
|
||
{
|
||
public string TableName { get; set; }
|
||
public bool IsValid { get; set; }
|
||
public string Message { get; set; }
|
||
public List<string> MissingColumns { get; set; }
|
||
public List<string> ExtraColumns { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 資料庫健康檢查結果
|
||
/// </summary>
|
||
public class DatabaseHealthCheckResult
|
||
{
|
||
public DateTime CheckTime { get; set; }
|
||
public bool IsHealthy { get; set; }
|
||
public bool AllTablesExist { get; set; }
|
||
public bool AllTablesValid { get; set; }
|
||
public List<string> MissingTables { get; set; }
|
||
public Dictionary<string, TableStructureValidationResult> TableValidationResults { get; set; }
|
||
public List<TableStatistics> TablesStatistics { get; set; }
|
||
public long DatabaseFileSize { get; set; }
|
||
public string DatabaseFileSizeFormatted { get; set; }
|
||
public string ErrorMessage { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 表格修復結果
|
||
/// </summary>
|
||
public class TableRepairResult
|
||
{
|
||
public string TableName { get; set; }
|
||
public bool IsRepaired { get; set; }
|
||
public string Message { get; set; }
|
||
public string Action { get; set; } // "CreateTable", "AddColumns", "NoAction"
|
||
public List<string> AddedColumns { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 資料庫修復結果
|
||
/// </summary>
|
||
public class DatabaseRepairResult
|
||
{
|
||
public DateTime RepairTime { get; set; }
|
||
public bool IsSuccessful { get; set; }
|
||
public List<TableRepairResult> TableRepairResults { get; set; }
|
||
public string ErrorMessage { get; set; }
|
||
|
||
public DatabaseRepairResult()
|
||
{
|
||
TableRepairResults = new List<TableRepairResult>();
|
||
}
|
||
}
|
||
}
|