SqlSugarDatabaseProvider.cs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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. db.Ado.BeginTran();
  72. try
  73. {
  74. foreach (var (key, value) in changes)
  75. {
  76. var existsQuery = $"SELECT COUNT(1) FROM {Quote(tableName)} WHERE {Quote(keyColumn)} = @key";
  77. var exists = db.Ado.GetInt(existsQuery, new { key }) > 0;
  78. if (exists)
  79. {
  80. var updateQuery = $"UPDATE {Quote(tableName)} SET {Quote(valueColumn)} = @value WHERE {Quote(keyColumn)} = @key";
  81. db.Ado.ExecuteCommand(updateQuery, new { key, value });
  82. }
  83. else
  84. {
  85. var insertQuery = $"INSERT INTO {Quote(tableName)} ({Quote(keyColumn)}, {Quote(valueColumn)}) VALUES (@key, @value)";
  86. db.Ado.ExecuteCommand(insertQuery, new { key, value });
  87. }
  88. }
  89. db.Ado.CommitTran();
  90. }
  91. catch
  92. {
  93. db.Ado.RollbackTran();
  94. throw;
  95. }
  96. }, cancellationToken);
  97. }
  98. private SqlSugarClient CreateDb(string connectionString)
  99. {
  100. return new SqlSugarClient(new ConnectionConfig
  101. {
  102. ConnectionString = connectionString,
  103. DbType = _dbType,
  104. IsAutoCloseConnection = false
  105. });
  106. }
  107. private string Quote(string identifier)
  108. {
  109. return _dbType switch
  110. {
  111. DbType.SqlServer => $"[{identifier}]",
  112. DbType.MySql => $"`{identifier}`",
  113. DbType.PostgreSQL => $"\"{identifier}\"",
  114. DbType.Oracle => $"\"{identifier.ToUpperInvariant()}\"",
  115. DbType.Sqlite => $"\"{identifier}\"",
  116. _ => identifier
  117. };
  118. }
  119. }