MySqlDatabase.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using MySqlConnector;
  7. using TeamAAS.Database.Attributes;
  8. using TeamAAS.Database.Interfaces;
  9. using TeamAAS.Database.Models;
  10. namespace TeamAAS.Database.Providers
  11. {
  12. /// <summary>
  13. /// MySQL 数据库提供者实现
  14. /// </summary>
  15. [DatabaseProvider("mysql", DisplayName = "MySQL", RequiresServer = true,
  16. Description = "MySQL / MariaDB 数据库,基于 MySqlConnector")]
  17. public class MySqlDatabase : IDatabase
  18. {
  19. private MySqlConnection _connection;
  20. private DatabaseConfig _config;
  21. private readonly object _sync = new object();
  22. public Guid Id { get; private set; }
  23. public string Name { get; set; }
  24. public string ProviderType => "mysql";
  25. public bool IsConnected => _connection != null && _connection.State == ConnectionState.Open;
  26. public string ConnectionString
  27. {
  28. get
  29. {
  30. if (_config == null) return string.Empty;
  31. if (!string.IsNullOrWhiteSpace(_config.ConnectionString))
  32. return MaskPassword(_config.ConnectionString);
  33. var sb = new StringBuilder();
  34. sb.Append($"Server={_config.Server};");
  35. if (_config.Port > 0) sb.Append($"Port={_config.Port};");
  36. sb.Append($"Database={_config.DatabaseName};");
  37. sb.Append($"Uid={_config.UserId};");
  38. sb.Append("Pwd=***;");
  39. if (_config.ConnectionTimeout > 0)
  40. sb.Append($"Connection Timeout={_config.ConnectionTimeout};");
  41. return sb.ToString();
  42. }
  43. }
  44. private string GetRawConnectionString()
  45. {
  46. if (!string.IsNullOrWhiteSpace(_config?.ConnectionString))
  47. return _config.ConnectionString;
  48. if (_config == null)
  49. throw new InvalidOperationException("数据库未配置");
  50. var sb = new MySqlConnectionStringBuilder
  51. {
  52. Server = _config.Server,
  53. Port = (uint)(_config.Port > 0 ? _config.Port : 3306),
  54. Database = _config.DatabaseName,
  55. UserID = _config.UserId,
  56. Password = _config.Password,
  57. ConnectionTimeout = (uint)(_config.ConnectionTimeout > 0 ? _config.ConnectionTimeout : 30)
  58. };
  59. if (_config.ExtraParams != null)
  60. {
  61. foreach (var kv in _config.ExtraParams)
  62. {
  63. try { sb[kv.Key] = kv.Value; } catch { /* 忽略无效参数 */ }
  64. }
  65. }
  66. return sb.ConnectionString;
  67. }
  68. public void Configure(DatabaseConfig config)
  69. {
  70. _config = config ?? throw new ArgumentNullException(nameof(config));
  71. Id = config.Id == Guid.Empty ? Guid.NewGuid() : config.Id;
  72. Name = config.Name ?? $"MySQL_{_config.Server}";
  73. }
  74. public bool Open()
  75. {
  76. lock (_sync)
  77. {
  78. try
  79. {
  80. if (_connection == null)
  81. _connection = new MySqlConnection(GetRawConnectionString());
  82. if (_connection.State == ConnectionState.Open)
  83. return true;
  84. _connection.Open();
  85. return true;
  86. }
  87. catch
  88. {
  89. return false;
  90. }
  91. }
  92. }
  93. public Task<bool> OpenAsync()
  94. {
  95. return Task.Run(() => Open());
  96. }
  97. public void Close()
  98. {
  99. lock (_sync)
  100. {
  101. if (_connection != null)
  102. {
  103. try { _connection.Close(); } catch { }
  104. }
  105. }
  106. }
  107. public Task CloseAsync()
  108. {
  109. return Task.Run(() => Close());
  110. }
  111. public (bool Success, string Message) TestConnection()
  112. {
  113. try
  114. {
  115. using (var conn = new MySqlConnection(GetRawConnectionString()))
  116. {
  117. conn.Open();
  118. return (true, "连接成功");
  119. }
  120. }
  121. catch (Exception ex)
  122. {
  123. return (false, ex.Message);
  124. }
  125. }
  126. public Task<(bool Success, string Message)> TestConnectionAsync()
  127. {
  128. return Task.Run(() => TestConnection());
  129. }
  130. public int ExecuteNonQuery(string sql, IDictionary<string, object> parameters = null)
  131. {
  132. using (var cmd = CreateCommand(sql, parameters))
  133. {
  134. EnsureOpen();
  135. return cmd.ExecuteNonQuery();
  136. }
  137. }
  138. public Task<int> ExecuteNonQueryAsync(string sql, IDictionary<string, object> parameters = null)
  139. {
  140. return Task.Run(() => ExecuteNonQuery(sql, parameters));
  141. }
  142. public DataTable ExecuteQuery(string sql, IDictionary<string, object> parameters = null)
  143. {
  144. using (var cmd = CreateCommand(sql, parameters))
  145. {
  146. EnsureOpen();
  147. var dt = new DataTable();
  148. using (var reader = cmd.ExecuteReader())
  149. {
  150. dt.Load(reader);
  151. }
  152. return dt;
  153. }
  154. }
  155. public Task<DataTable> ExecuteQueryAsync(string sql, IDictionary<string, object> parameters = null)
  156. {
  157. return Task.Run(() => ExecuteQuery(sql, parameters));
  158. }
  159. public object ExecuteScalar(string sql, IDictionary<string, object> parameters = null)
  160. {
  161. using (var cmd = CreateCommand(sql, parameters))
  162. {
  163. EnsureOpen();
  164. return cmd.ExecuteScalar();
  165. }
  166. }
  167. public Task<object> ExecuteScalarAsync(string sql, IDictionary<string, object> parameters = null)
  168. {
  169. return Task.Run(() => ExecuteScalar(sql, parameters));
  170. }
  171. public bool ExecuteTransaction(IEnumerable<string> sqlCommands)
  172. {
  173. if (sqlCommands == null) return true;
  174. EnsureOpen();
  175. using (var transaction = _connection.BeginTransaction())
  176. {
  177. try
  178. {
  179. foreach (var sql in sqlCommands)
  180. {
  181. if (string.IsNullOrWhiteSpace(sql)) continue;
  182. using (var cmd = _connection.CreateCommand())
  183. {
  184. cmd.Transaction = transaction;
  185. cmd.CommandText = sql;
  186. cmd.ExecuteNonQuery();
  187. }
  188. }
  189. transaction.Commit();
  190. return true;
  191. }
  192. catch
  193. {
  194. try { transaction.Rollback(); } catch { }
  195. return false;
  196. }
  197. }
  198. }
  199. public Task<bool> ExecuteTransactionAsync(IEnumerable<string> sqlCommands)
  200. {
  201. return Task.Run(() => ExecuteTransaction(sqlCommands));
  202. }
  203. public List<string> GetTableNames()
  204. {
  205. var tables = new List<string>();
  206. var sql = "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = @dbName ORDER BY TABLE_NAME";
  207. var parameters = new Dictionary<string, object> { { "@dbName", _config.DatabaseName } };
  208. using (var cmd = CreateCommand(sql, parameters))
  209. {
  210. EnsureOpen();
  211. using (var reader = cmd.ExecuteReader())
  212. {
  213. while (reader.Read())
  214. {
  215. tables.Add(reader.GetString(0));
  216. }
  217. }
  218. }
  219. return tables;
  220. }
  221. public Task<List<string>> GetTableNamesAsync()
  222. {
  223. return Task.Run(() => GetTableNames());
  224. }
  225. public List<ColumnInfo> GetTableSchema(string tableName)
  226. {
  227. var columns = new List<ColumnInfo>();
  228. var sql = @"
  229. SELECT
  230. COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_KEY,
  231. CHARACTER_MAXIMUM_LENGTH, COLUMN_DEFAULT, COLUMN_COMMENT
  232. FROM information_schema.COLUMNS
  233. WHERE TABLE_SCHEMA = @dbName AND TABLE_NAME = @tableName
  234. ORDER BY ORDINAL_POSITION";
  235. var parameters = new Dictionary<string, object>
  236. {
  237. { "@dbName", _config.DatabaseName },
  238. { "@tableName", tableName }
  239. };
  240. using (var cmd = CreateCommand(sql, parameters))
  241. {
  242. EnsureOpen();
  243. using (var reader = cmd.ExecuteReader())
  244. {
  245. while (reader.Read())
  246. {
  247. columns.Add(new ColumnInfo
  248. {
  249. ColumnName = reader.GetString(0),
  250. DataType = reader.GetString(1),
  251. IsNullable = reader.GetString(2) == "YES",
  252. IsPrimaryKey = reader.GetString(3) == "PRI",
  253. MaxLength = reader.IsDBNull(4) ? 0 : Convert.ToInt32(reader[4]),
  254. DefaultValue = reader.IsDBNull(5) ? null : reader.GetString(5),
  255. Description = reader.IsDBNull(6) ? null : reader.GetString(6)
  256. });
  257. }
  258. }
  259. }
  260. return columns;
  261. }
  262. public Task<List<ColumnInfo>> GetTableSchemaAsync(string tableName)
  263. {
  264. return Task.Run(() => GetTableSchema(tableName));
  265. }
  266. #region 私有方法
  267. private MySqlCommand CreateCommand(string sql, IDictionary<string, object> parameters)
  268. {
  269. var cmd = _connection.CreateCommand();
  270. cmd.CommandText = sql;
  271. if (parameters != null)
  272. {
  273. foreach (var kv in parameters)
  274. {
  275. var paramName = kv.Key.StartsWith("@") ? kv.Key : "@" + kv.Key;
  276. cmd.Parameters.AddWithValue(paramName, kv.Value ?? DBNull.Value);
  277. }
  278. }
  279. return cmd;
  280. }
  281. private void EnsureOpen()
  282. {
  283. if (_connection == null || _connection.State != ConnectionState.Open)
  284. {
  285. if (!Open())
  286. throw new InvalidOperationException("数据库连接失败");
  287. }
  288. }
  289. private static string MaskPassword(string connStr)
  290. {
  291. try
  292. {
  293. var builder = new MySqlConnectionStringBuilder(connStr);
  294. if (!string.IsNullOrWhiteSpace(builder.Password))
  295. builder.Password = "***";
  296. return builder.ConnectionString;
  297. }
  298. catch
  299. {
  300. return connStr;
  301. }
  302. }
  303. #endregion
  304. #region IDisposable
  305. private bool _disposed;
  306. public void Dispose()
  307. {
  308. Dispose(true);
  309. GC.SuppressFinalize(this);
  310. }
  311. protected virtual void Dispose(bool disposing)
  312. {
  313. if (_disposed) return;
  314. if (disposing)
  315. {
  316. Close();
  317. _connection?.Dispose();
  318. _connection = null;
  319. }
  320. _disposed = true;
  321. }
  322. ~MySqlDatabase()
  323. {
  324. Dispose(false);
  325. }
  326. #endregion
  327. }
  328. }