SqlSugarDatabaseProvider.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. using System.Text.RegularExpressions;
  2. using SqlSugar;
  3. namespace Apq.Cfg.Database;
  4. /// <summary>
  5. /// 基于 SqlSugar 的统一数据库配置提供程序
  6. /// </summary>
  7. internal sealed partial class SqlSugarDatabaseProvider
  8. {
  9. private readonly DbType _dbType;
  10. #if NET7_0_OR_GREATER
  11. [GeneratedRegex(@"^[a-zA-Z_][a-zA-Z0-9_]*$", RegexOptions.Compiled)]
  12. private static partial Regex SafeIdentifierRegex();
  13. #else
  14. private static readonly Regex _safeIdentifierRegex = new(@"^[a-zA-Z_][a-zA-Z0-9_]*$", RegexOptions.Compiled);
  15. private static Regex SafeIdentifierRegex() => _safeIdentifierRegex;
  16. #endif
  17. public SqlSugarDatabaseProvider(DbType dbType)
  18. {
  19. _dbType = dbType;
  20. }
  21. private static void ValidateIdentifier(string identifier, string paramName)
  22. {
  23. if (string.IsNullOrWhiteSpace(identifier))
  24. throw new ArgumentException($"标识符 '{paramName}' 不能为空", paramName);
  25. if (identifier.Length > 128)
  26. throw new ArgumentException($"标识符 '{paramName}' 长度不能超过 128 个字符", paramName);
  27. if (!SafeIdentifierRegex().IsMatch(identifier))
  28. throw new ArgumentException(
  29. $"标识符 '{paramName}' 包含非法字符。只允许字母、数字和下划线,且不能以数字开头。值: '{identifier}'",
  30. paramName);
  31. }
  32. public async Task<Dictionary<string, string?>> LoadConfigurationAsync(
  33. string connectionString,
  34. string tableName,
  35. string keyColumn,
  36. string valueColumn,
  37. CancellationToken cancellationToken = default)
  38. {
  39. ValidateIdentifier(tableName, nameof(tableName));
  40. ValidateIdentifier(keyColumn, nameof(keyColumn));
  41. ValidateIdentifier(valueColumn, nameof(valueColumn));
  42. var result = new Dictionary<string, string?>();
  43. using var db = CreateDb(connectionString);
  44. var query = $"SELECT {Quote(keyColumn)}, {Quote(valueColumn)} FROM {Quote(tableName)}";
  45. var dataTable = await Task.Run(() => db.Ado.GetDataTable(query), cancellationToken);
  46. foreach (System.Data.DataRow row in dataTable.Rows)
  47. {
  48. var key = row[keyColumn]?.ToString();
  49. if (key != null)
  50. {
  51. var value = row[valueColumn] == DBNull.Value ? null : row[valueColumn]?.ToString();
  52. result[key] = value;
  53. }
  54. }
  55. return result;
  56. }
  57. public async Task ApplyChangesAsync(
  58. string connectionString,
  59. string tableName,
  60. string keyColumn,
  61. string valueColumn,
  62. IReadOnlyDictionary<string, string?> changes,
  63. CancellationToken cancellationToken = default)
  64. {
  65. ValidateIdentifier(tableName, nameof(tableName));
  66. ValidateIdentifier(keyColumn, nameof(keyColumn));
  67. ValidateIdentifier(valueColumn, nameof(valueColumn));
  68. using var db = CreateDb(connectionString);
  69. await Task.Run(() =>
  70. {
  71. // 确保表存在
  72. EnsureTableExists(db, tableName, keyColumn, valueColumn);
  73. db.Ado.BeginTran();
  74. try
  75. {
  76. foreach (var (key, value) in changes)
  77. {
  78. var existsQuery = $"SELECT COUNT(1) FROM {Quote(tableName)} WHERE {Quote(keyColumn)} = @key";
  79. var exists = db.Ado.GetInt(existsQuery, new { key }) > 0;
  80. if (exists)
  81. {
  82. var updateQuery = $"UPDATE {Quote(tableName)} SET {Quote(valueColumn)} = @value WHERE {Quote(keyColumn)} = @key";
  83. db.Ado.ExecuteCommand(updateQuery, new { key, value });
  84. }
  85. else
  86. {
  87. var insertQuery = $"INSERT INTO {Quote(tableName)} ({Quote(keyColumn)}, {Quote(valueColumn)}) VALUES (@key, @value)";
  88. db.Ado.ExecuteCommand(insertQuery, new { key, value });
  89. }
  90. }
  91. db.Ado.CommitTran();
  92. }
  93. catch
  94. {
  95. db.Ado.RollbackTran();
  96. throw;
  97. }
  98. }, cancellationToken);
  99. }
  100. private SqlSugarClient CreateDb(string connectionString)
  101. {
  102. return new SqlSugarClient(new ConnectionConfig
  103. {
  104. ConnectionString = connectionString,
  105. DbType = _dbType,
  106. IsAutoCloseConnection = false
  107. });
  108. }
  109. private void EnsureTableExists(SqlSugarClient db, string tableName, string keyColumn, string valueColumn)
  110. {
  111. var createSql = _dbType switch
  112. {
  113. DbType.SqlServer => $@"
  114. IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = '{tableName}')
  115. CREATE TABLE [{tableName}] (
  116. [{keyColumn}] NVARCHAR(512) NOT NULL PRIMARY KEY,
  117. [{valueColumn}] NVARCHAR(MAX) NULL
  118. )",
  119. DbType.MySql => $@"
  120. CREATE TABLE IF NOT EXISTS `{tableName}` (
  121. `{keyColumn}` VARCHAR(512) NOT NULL PRIMARY KEY,
  122. `{valueColumn}` TEXT NULL
  123. )",
  124. DbType.PostgreSQL => $@"
  125. CREATE TABLE IF NOT EXISTS ""{tableName}"" (
  126. ""{keyColumn}"" VARCHAR(512) NOT NULL PRIMARY KEY,
  127. ""{valueColumn}"" TEXT NULL
  128. )",
  129. DbType.Sqlite => $@"
  130. CREATE TABLE IF NOT EXISTS ""{tableName}"" (
  131. ""{keyColumn}"" TEXT NOT NULL PRIMARY KEY,
  132. ""{valueColumn}"" TEXT NULL
  133. )",
  134. DbType.Oracle => $@"
  135. BEGIN
  136. EXECUTE IMMEDIATE 'CREATE TABLE ""{tableName.ToUpperInvariant()}"" (
  137. ""{keyColumn.ToUpperInvariant()}"" VARCHAR2(512) NOT NULL PRIMARY KEY,
  138. ""{valueColumn.ToUpperInvariant()}"" CLOB NULL
  139. )';
  140. EXCEPTION WHEN OTHERS THEN
  141. IF SQLCODE != -955 THEN RAISE; END IF;
  142. END;",
  143. _ => throw new NotSupportedException($"不支持的数据库类型: {_dbType}")
  144. };
  145. db.Ado.ExecuteCommand(createSql);
  146. }
  147. private string Quote(string identifier)
  148. {
  149. return _dbType switch
  150. {
  151. DbType.SqlServer => $"[{identifier}]",
  152. DbType.MySql => $"`{identifier}`",
  153. DbType.PostgreSQL => $"\"{identifier}\"",
  154. DbType.Oracle => $"\"{identifier.ToUpperInvariant()}\"",
  155. DbType.Sqlite => $"\"{identifier}\"",
  156. _ => identifier
  157. };
  158. }
  159. }