SqliteDatabase.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Data.SQLite;
  5. using System.IO;
  6. using System.Threading.Tasks;
  7. using TeamAAS.Database.Attributes;
  8. using TeamAAS.Database.Interfaces;
  9. using TeamAAS.Database.Models;
  10. namespace TeamAAS.Database.Providers
  11. {
  12. /// <summary>
  13. /// SQLite 数据库提供者实现
  14. /// </summary>
  15. [DatabaseProvider("sqlite", DisplayName = "SQLite", RequiresServer = false,
  16. Description = "SQLite 文件型数据库,基于 System.Data.SQLite")]
  17. public class SqliteDatabase : IDatabase
  18. {
  19. private SQLiteConnection _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 => "sqlite";
  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 _config.ConnectionString;
  33. return $"Data Source={GetDbFilePath()};Version=3;";
  34. }
  35. }
  36. private string GetDbFilePath()
  37. {
  38. // SQLite: Server 字段存文件路径,或者 DatabaseName 存文件名
  39. if (!string.IsNullOrWhiteSpace(_config.Server) && _config.Server.Contains("\\"))
  40. return _config.Server;
  41. if (!string.IsNullOrWhiteSpace(_config.DatabaseName) && _config.DatabaseName.Contains("\\"))
  42. return _config.DatabaseName;
  43. var fileName = string.IsNullOrWhiteSpace(_config.DatabaseName)
  44. ? _config.Server
  45. : _config.DatabaseName;
  46. if (string.IsNullOrWhiteSpace(fileName))
  47. fileName = "database.db";
  48. if (!Path.IsPathRooted(fileName))
  49. fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
  50. return fileName;
  51. }
  52. private string GetRawConnectionString()
  53. {
  54. if (!string.IsNullOrWhiteSpace(_config?.ConnectionString))
  55. return _config.ConnectionString;
  56. if (_config == null)
  57. throw new InvalidOperationException("数据库未配置");
  58. var filePath = GetDbFilePath();
  59. var builder = new SQLiteConnectionStringBuilder
  60. {
  61. DataSource = filePath,
  62. Version = 3,
  63. FailIfMissing = false,
  64. JournalMode = SQLiteJournalModeEnum.Wal
  65. };
  66. if (_config.ConnectionTimeout > 0)
  67. builder.DefaultTimeout = _config.ConnectionTimeout;
  68. if (_config.ExtraParams != null)
  69. {
  70. foreach (var kv in _config.ExtraParams)
  71. {
  72. try
  73. {
  74. var prop = typeof(SQLiteConnectionStringBuilder).GetProperty(kv.Key);
  75. if (prop != null && prop.CanWrite)
  76. prop.SetValue(builder, Convert.ChangeType(kv.Value, prop.PropertyType));
  77. }
  78. catch { /* 忽略无效参数 */ }
  79. }
  80. }
  81. return builder.ConnectionString;
  82. }
  83. public void Configure(DatabaseConfig config)
  84. {
  85. _config = config ?? throw new ArgumentNullException(nameof(config));
  86. Id = config.Id == Guid.Empty ? Guid.NewGuid() : config.Id;
  87. Name = config.Name ?? $"SQLite_{Path.GetFileName(GetDbFilePath())}";
  88. }
  89. public bool Open()
  90. {
  91. lock (_sync)
  92. {
  93. try
  94. {
  95. if (_connection == null)
  96. {
  97. var filePath = GetDbFilePath();
  98. var dir = Path.GetDirectoryName(filePath);
  99. if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
  100. Directory.CreateDirectory(dir);
  101. _connection = new SQLiteConnection(GetRawConnectionString());
  102. }
  103. if (_connection.State == ConnectionState.Open)
  104. return true;
  105. _connection.Open();
  106. return true;
  107. }
  108. catch
  109. {
  110. return false;
  111. }
  112. }
  113. }
  114. public Task<bool> OpenAsync()
  115. {
  116. return Task.Run(() => Open());
  117. }
  118. public void Close()
  119. {
  120. lock (_sync)
  121. {
  122. if (_connection != null)
  123. {
  124. try { _connection.Close(); } catch { }
  125. }
  126. }
  127. }
  128. public Task CloseAsync()
  129. {
  130. return Task.Run(() => Close());
  131. }
  132. public (bool Success, string Message) TestConnection()
  133. {
  134. try
  135. {
  136. using (var conn = new SQLiteConnection(GetRawConnectionString()))
  137. {
  138. conn.Open();
  139. return (true, "连接成功");
  140. }
  141. }
  142. catch (Exception ex)
  143. {
  144. return (false, ex.Message);
  145. }
  146. }
  147. public Task<(bool Success, string Message)> TestConnectionAsync()
  148. {
  149. return Task.Run(() => TestConnection());
  150. }
  151. public int ExecuteNonQuery(string sql, IDictionary<string, object> parameters = null)
  152. {
  153. using (var cmd = CreateCommand(sql, parameters))
  154. {
  155. EnsureOpen();
  156. return cmd.ExecuteNonQuery();
  157. }
  158. }
  159. public Task<int> ExecuteNonQueryAsync(string sql, IDictionary<string, object> parameters = null)
  160. {
  161. return Task.Run(() => ExecuteNonQuery(sql, parameters));
  162. }
  163. public DataTable ExecuteQuery(string sql, IDictionary<string, object> parameters = null)
  164. {
  165. using (var cmd = CreateCommand(sql, parameters))
  166. {
  167. EnsureOpen();
  168. var dt = new DataTable();
  169. using (var reader = cmd.ExecuteReader())
  170. {
  171. dt.Load(reader);
  172. }
  173. return dt;
  174. }
  175. }
  176. public Task<DataTable> ExecuteQueryAsync(string sql, IDictionary<string, object> parameters = null)
  177. {
  178. return Task.Run(() => ExecuteQuery(sql, parameters));
  179. }
  180. public object ExecuteScalar(string sql, IDictionary<string, object> parameters = null)
  181. {
  182. using (var cmd = CreateCommand(sql, parameters))
  183. {
  184. EnsureOpen();
  185. return cmd.ExecuteScalar();
  186. }
  187. }
  188. public Task<object> ExecuteScalarAsync(string sql, IDictionary<string, object> parameters = null)
  189. {
  190. return Task.Run(() => ExecuteScalar(sql, parameters));
  191. }
  192. public bool ExecuteTransaction(IEnumerable<string> sqlCommands)
  193. {
  194. if (sqlCommands == null) return true;
  195. EnsureOpen();
  196. using (var transaction = _connection.BeginTransaction())
  197. {
  198. try
  199. {
  200. foreach (var sql in sqlCommands)
  201. {
  202. if (string.IsNullOrWhiteSpace(sql)) continue;
  203. using (var cmd = _connection.CreateCommand())
  204. {
  205. cmd.Transaction = transaction;
  206. cmd.CommandText = sql;
  207. cmd.ExecuteNonQuery();
  208. }
  209. }
  210. transaction.Commit();
  211. return true;
  212. }
  213. catch
  214. {
  215. try { transaction.Rollback(); } catch { }
  216. return false;
  217. }
  218. }
  219. }
  220. public Task<bool> ExecuteTransactionAsync(IEnumerable<string> sqlCommands)
  221. {
  222. return Task.Run(() => ExecuteTransaction(sqlCommands));
  223. }
  224. public List<string> GetTableNames()
  225. {
  226. var tables = new List<string>();
  227. var sql = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name";
  228. using (var cmd = CreateCommand(sql, null))
  229. {
  230. EnsureOpen();
  231. using (var reader = cmd.ExecuteReader())
  232. {
  233. while (reader.Read())
  234. {
  235. tables.Add(reader.GetString(0));
  236. }
  237. }
  238. }
  239. return tables;
  240. }
  241. public Task<List<string>> GetTableNamesAsync()
  242. {
  243. return Task.Run(() => GetTableNames());
  244. }
  245. public List<ColumnInfo> GetTableSchema(string tableName)
  246. {
  247. var columns = new List<ColumnInfo>();
  248. var sql = $"PRAGMA table_info([{tableName}])";
  249. using (var cmd = CreateCommand(sql, null))
  250. {
  251. EnsureOpen();
  252. using (var reader = cmd.ExecuteReader())
  253. {
  254. while (reader.Read())
  255. {
  256. // cid, name, type, notnull, dflt_value, pk
  257. var colName = reader["name"].ToString();
  258. columns.Add(new ColumnInfo
  259. {
  260. ColumnName = colName,
  261. DataType = reader["type"].ToString(),
  262. IsNullable = Convert.ToInt32(reader["notnull"]) == 0,
  263. IsPrimaryKey = Convert.ToInt32(reader["pk"]) > 0,
  264. DefaultValue = reader["dflt_value"] == DBNull.Value ? null : reader["dflt_value"].ToString(),
  265. MaxLength = 0,
  266. Description = null
  267. });
  268. }
  269. }
  270. }
  271. return columns;
  272. }
  273. public Task<List<ColumnInfo>> GetTableSchemaAsync(string tableName)
  274. {
  275. return Task.Run(() => GetTableSchema(tableName));
  276. }
  277. #region 私有方法
  278. private SQLiteCommand CreateCommand(string sql, IDictionary<string, object> parameters)
  279. {
  280. var cmd = _connection.CreateCommand();
  281. cmd.CommandText = sql;
  282. if (parameters != null)
  283. {
  284. foreach (var kv in parameters)
  285. {
  286. var paramName = kv.Key.StartsWith("@") ? kv.Key : "@" + kv.Key;
  287. cmd.Parameters.AddWithValue(paramName, kv.Value ?? DBNull.Value);
  288. }
  289. }
  290. return cmd;
  291. }
  292. private void EnsureOpen()
  293. {
  294. if (_connection == null || _connection.State != ConnectionState.Open)
  295. {
  296. if (!Open())
  297. throw new InvalidOperationException("数据库连接失败");
  298. }
  299. }
  300. #endregion
  301. #region IDisposable
  302. private bool _disposed;
  303. public void Dispose()
  304. {
  305. Dispose(true);
  306. GC.SuppressFinalize(this);
  307. }
  308. protected virtual void Dispose(bool disposing)
  309. {
  310. if (_disposed) return;
  311. if (disposing)
  312. {
  313. Close();
  314. _connection?.Dispose();
  315. _connection = null;
  316. }
  317. _disposed = true;
  318. }
  319. ~SqliteDatabase()
  320. {
  321. Dispose(false);
  322. }
  323. #endregion
  324. }
  325. }