You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

534 lines
20 KiB

3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Data.SQLite;
  5. using System.IO;
  6. using System.Text;
  7. //using Mono.Data.Sqlite;
  8. using static Apewer.Source.SourceUtility;
  9. namespace Apewer.Source
  10. {
  11. /// <summary>连接 SQLite 数据库的客户端。</summary>
  12. public class Sqlite : DbClient
  13. {
  14. #region connection
  15. private SQLiteConnection _conn = null;
  16. private string _connstr = null;
  17. private string _path = null;
  18. private string _pass = null;
  19. /// <summary>连接字符串。</summary>
  20. public override string ConnectionString => _connstr;
  21. /// <summary>当前数据库的文件路径。</summary>
  22. public string Path { get => _path; }
  23. /// <summary>使用现有的连接创建实例。</summary>
  24. /// <param name="connection">有效的 SQLite 连接。</param>
  25. /// <param name="timeout">超时设定。</param>
  26. /// <exception cref="ArgumentNullException"></exception>
  27. /// <exception cref="ArgumentException"></exception>
  28. public Sqlite(IDbConnection connection, Timeout timeout = null) : base(timeout)
  29. {
  30. if (connection == null) throw new ArgumentNullException(nameof(connection), "指定的连接无效。");
  31. var conn = connection as SQLiteConnection;
  32. if (conn == null) throw new ArgumentException(nameof(connection), "指定的连接不是支持的类型。");
  33. _conn = conn;
  34. _connstr = conn.ConnectionString;
  35. }
  36. /// <summary>创建连接实例。</summary>
  37. /// <param name="path">数据库的文件路径,指定为空时将使用 :memory: 作为路径。</param>
  38. /// <param name="pass">连接数据库的密码,使用内存数据库时此参数将被忽略。</param>
  39. /// <param name="timeout">超时设定。</param>
  40. /// <exception cref="FileNotFoundException"></exception>
  41. public Sqlite(string path = null, string pass = null, Timeout timeout = null) : base(timeout)
  42. {
  43. // 使用内存。
  44. if (string.IsNullOrEmpty(path) || path.ToLower() == Memory)
  45. {
  46. _connstr = "data source=':memory:'; version=3; ";
  47. _path = Memory;
  48. return;
  49. }
  50. // 使用文件。
  51. if (!File.Exists(path)) throw new FileNotFoundException("文件不存在。", path);
  52. _connstr = $"data source='{_path}'; version=3; ";
  53. _path = path;
  54. if (!string.IsNullOrEmpty(pass))
  55. {
  56. pass = pass.Trim();
  57. if (!string.IsNullOrEmpty(pass))
  58. {
  59. _connstr += $"password={_pass}; ";
  60. _pass = pass;
  61. }
  62. }
  63. }
  64. /// <summary>
  65. /// <para>挂载数据库。</para>
  66. /// <para>attach database "<paramref name="path"/>" as "<paramref name="alias"/>"</para>
  67. /// </summary>
  68. public string Attach(string path, string alias)
  69. {
  70. if (path.IsEmpty()) return "未指定数据库路径。";
  71. if (alias.IsEmpty()) return "未指定别名。";
  72. var connect = Connect();
  73. if (connect.NotEmpty()) return connect;
  74. var sql = $"attach database \"{path}\" as \"{alias}\"";
  75. var execute = Execute(sql, null, false) as Execute;
  76. return execute.Success ? null : execute.Message;
  77. }
  78. /// <summary>
  79. /// <para>卸载数据库。</para>
  80. /// <para>detach database "<paramref name="alias"/>"</para>
  81. /// </summary>
  82. public string Detach(string alias)
  83. {
  84. if (alias.IsEmpty()) return "未指定别名。";
  85. var connect = Connect();
  86. if (connect.NotEmpty()) return connect;
  87. var sql = $"detach \"{alias}\"";
  88. var execute = Execute(sql, null, false) as Execute;
  89. return execute.Success ? null : execute.Message;
  90. }
  91. #endregion
  92. #region public
  93. /// <exception cref="NotImplementedException"></exception>
  94. public override string[] StoreNames() => throw new NotImplementedException();
  95. /// <summary></summary>
  96. public override string[] TableNames() => QueryStrings("select name from sqlite_master where type='table' order by name");
  97. /// <summary></summary>
  98. public override string[] ColumnNames(string tableName) => QueryStrings($"pragma table_info('{tableName.SafeName()}'); ");
  99. /// <summary>插入记录。返回错误信息。</summary>
  100. public override string Insert(object record, string table = null, bool adjust = true)
  101. {
  102. if (record == null) return "参数无效。";
  103. if (adjust) SourceUtility.FixProperties(record);
  104. // 解析模型,获取表名。
  105. var structure = TableStructure.Parse(record.GetType());
  106. if (structure == null) return "无法解析记录模型。";
  107. if (string.IsNullOrEmpty(table)) table = structure.TableName;
  108. if (string.IsNullOrEmpty(table)) return "表名称无效。";
  109. // 排除字段。
  110. var excluded = new List<string>();
  111. foreach (var ca in structure.Columns)
  112. {
  113. // 排除不使用 ORM 的属性。
  114. if (ca.Independent || ca.Incremental) excluded.Add(ca.Field);
  115. }
  116. // 准备参数。
  117. var ps = structure.CreateParameters(record, Parameter, excluded);
  118. var psc = ps.Length;
  119. if (psc < 1) return "数据模型不包含字段。";
  120. // 合成 SQL 语句。
  121. var sb = new StringBuilder();
  122. sb.Append("insert into ");
  123. sb.Append(table);
  124. sb.Append("(");
  125. for (var i = 0; i < psc; i++)
  126. {
  127. if (i > 0) sb.Append(", ");
  128. sb.Append(ps[i].ParameterName);
  129. }
  130. sb.Append(") values(");
  131. for (var i = 0; i < psc; i++)
  132. {
  133. if (i > 0) sb.Append(", ");
  134. sb.Append("@");
  135. sb.Append(ps[i].ParameterName);
  136. }
  137. sb.Append("); ");
  138. var sql = sb.ToString();
  139. // 执行。
  140. var execute = Execute(sql, ps, false);
  141. if (execute.Success && execute.Rows > 0) return TextUtility.Empty;
  142. return execute.Message;
  143. }
  144. /// <summary>更新记录,实体中的 Key 属性不被更新。返回错误信息。</summary>
  145. /// <remarks>无法更新带有 Independent 特性的模型(缺少 Key 属性)。</remarks>
  146. public override string Update(IRecord record, string table = null, bool adjust = true)
  147. {
  148. if (record == null) return "参数无效。";
  149. if (adjust)
  150. {
  151. FixProperties(record);
  152. SetUpdated(record);
  153. }
  154. // 解析模型,获取表名。
  155. var structure = TableStructure.Parse(record.GetType());
  156. if (structure == null) return "无法解析记录模型。";
  157. if (structure.Independent) return "无法更新带有 Independent 特性的模型。";
  158. if (string.IsNullOrEmpty(table)) table = structure.TableName;
  159. if (string.IsNullOrEmpty(table)) return "表名称无效。";
  160. // 排除字段。
  161. var excluded = new List<string>();
  162. if (structure.Key != null) excluded.Add(structure.Key.Field);
  163. foreach (var ca in structure.Columns)
  164. {
  165. // 排除不使用 ORM 的属性、自增属性和主键属性。
  166. if (ca.Independent || ca.Incremental || ca.PrimaryKey || ca.NoUpdate) excluded.Add(ca.Field);
  167. }
  168. // 准备参数。
  169. var ps = structure.CreateParameters(record, Parameter, excluded);
  170. var psc = ps.Length;
  171. if (psc < 1) return "数据模型不包含字段。";
  172. // 合成 SQL 语句。
  173. var cs = new List<string>();
  174. foreach (var p in ps) cs.Add($"[{p.ParameterName}] = @{p.ParameterName}");
  175. var sql = $"update {table} set {string.Join(", ", cs.ToArray())} where [{structure.Key.Field}] = '{record.Key.SafeKey()}'";
  176. // 执行。
  177. var execute = Execute(sql, ps, false);
  178. if (execute.Success && execute.Rows > 0) return TextUtility.Empty;
  179. return execute.Message;
  180. }
  181. #endregion
  182. #region protected
  183. /// <summary></summary>
  184. protected override string Initialize(TableStructure structure, string table)
  185. {
  186. if (string.IsNullOrEmpty(table)) table = structure.TableName;
  187. // 检查现存表。
  188. var exists = false;
  189. var tables = TableNames();
  190. if (tables.Length > 0)
  191. {
  192. var lower = table.ToLower();
  193. foreach (var tn in tables)
  194. {
  195. if (TextUtility.IsEmpty(tn)) continue;
  196. if (tn.ToLower() == lower)
  197. {
  198. exists = true;
  199. break;
  200. }
  201. }
  202. }
  203. if (exists)
  204. {
  205. return TextUtility.Merge("指定的表已经存在。");
  206. }
  207. else
  208. {
  209. var sqlcolumns = new List<string>();
  210. foreach (var column in structure.Columns)
  211. {
  212. var type = Declaration(column);
  213. if (string.IsNullOrEmpty(type))
  214. {
  215. return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。");
  216. }
  217. if (!column.Independent)
  218. {
  219. if (column.PrimaryKey) type = type + " primary key";
  220. }
  221. sqlcolumns.Add(type);
  222. }
  223. var sql = TextUtility.Merge("create table [", table, "](", TextUtility.Join(", ", sqlcolumns), "); ");
  224. var execute = Execute(sql, null, false);
  225. if (execute.Success) return TextUtility.Empty;
  226. return execute.Message;
  227. }
  228. }
  229. /// <summary></summary>
  230. protected override IDataAdapter CreateDataAdapter(IDbCommand command) => new SQLiteDataAdapter((SQLiteCommand)command);
  231. /// <summary></summary>
  232. protected override IDbConnection GetConnection()
  233. {
  234. if (_conn == null) _conn = new SQLiteConnection(_connstr);
  235. return _conn;
  236. }
  237. /// <summary></summary>
  238. protected override IDataParameter CreateParameter() => new SQLiteParameter();
  239. /// <summary></summary>
  240. protected override string Keys(string tableName, string keyField, string flagField, long flagValue)
  241. {
  242. if (flagValue == 0) return $"select [{keyField}] from [{tableName}] where [{flagField}] = {flagValue}";
  243. return $"select [{keyField}] from [{tableName}]";
  244. }
  245. /// <summary></summary>
  246. protected override string Get(string tableName, string keyField, string keyValue, string flagField, long flagValue)
  247. {
  248. if (flagValue == 0) return $"select * from [{tableName}] where [{keyField}] = '{keyValue}' limit 1";
  249. else return $"select * from [{tableName}] where [{keyField}] = '{keyValue}' and [{flagField}] = {flagValue} limit 1";
  250. }
  251. /// <summary></summary>
  252. protected override string List(string tableName, string flagField, long flagValue)
  253. {
  254. if (flagValue == 0) return $"select * from [{tableName}]";
  255. else return $"select * from [{tableName}] where [{flagField}] = {flagValue}";
  256. }
  257. #endregion
  258. #region special
  259. /// <summary>整理数据库,压缩未使用的空间。</summary>
  260. public const string Vacuum = "vacuum";
  261. /// <summary>内存数据库的地址。</summary>
  262. public const string Memory = ":memory:";
  263. /// <summary>查询数据库中的所有视图名。</summary>
  264. public string[] ViewNames() => QueryStrings("select name from sqlite_master where type='view' order by name");
  265. #endregion
  266. #region mirror
  267. /// <summary>保存当前数据库到文件,若文件已存在则将重写文件。</summary>
  268. public bool Save(string path, string pass = null)
  269. {
  270. if (!StorageUtility.CreateFile(path, 0, true))
  271. {
  272. Logger.Error(nameof(Sqlite), "Save", TextUtility.Merge("创建文件 ", path, " 失败。"));
  273. return false;
  274. }
  275. using (var destination = new Sqlite(path, pass)) return Save(destination);
  276. }
  277. /// <summary>保存当前数据库到目标数据库。</summary>
  278. public bool Save(Sqlite destination) => string.IsNullOrEmpty(Backup(this, destination));
  279. /// <summary>加载文件到当前数据库。</summary>
  280. public bool Load(string path, string pass = null)
  281. {
  282. using (var source = new Sqlite(path, pass)) return Load(source);
  283. }
  284. /// <summary>加载源数据库到当前数据库。</summary>
  285. public bool Load(Sqlite source) => string.IsNullOrEmpty(Backup(source, this));
  286. /// <summary>备份数据库,返回错误信息。</summary>
  287. static string Backup(Sqlite source, Sqlite destination)
  288. {
  289. if (source == null) return "备份失败:源无效。";
  290. if (destination == null) return "备份失败:目标无效。";
  291. var sConnect = source.Connect();
  292. if (sConnect.NotEmpty()) return sConnect;
  293. var dConnect = source.Connect();
  294. if (dConnect.NotEmpty()) return dConnect;
  295. lock (source.Locker)
  296. {
  297. lock (destination.Locker)
  298. {
  299. try
  300. {
  301. var src = (SQLiteConnection)source.Connection;
  302. var dst = (SQLiteConnection)destination.Connection;
  303. src.BackupDatabase(dst, "main", "main", -1, null, 0);
  304. return "";
  305. }
  306. catch (Exception ex)
  307. {
  308. return "SQLite Load Failed: " + ex.Message;
  309. }
  310. }
  311. }
  312. }
  313. #endregion
  314. #region type & parameter
  315. private static string GetColumnTypeName(ColumnType type)
  316. {
  317. switch (type)
  318. {
  319. case ColumnType.Bytes:
  320. return "blob";
  321. case ColumnType.Integer:
  322. return "integer";
  323. case ColumnType.Float:
  324. return "float";
  325. case ColumnType.VarChar:
  326. case ColumnType.VarChar191:
  327. case ColumnType.VarCharMax:
  328. return "varchar";
  329. case ColumnType.Text:
  330. return "text";
  331. case ColumnType.NVarChar:
  332. case ColumnType.NVarChar191:
  333. case ColumnType.NVarCharMax:
  334. return "nvarchar";
  335. case ColumnType.NText:
  336. return "ntext";
  337. default:
  338. return null;
  339. }
  340. }
  341. private static string Declaration(ColumnAttribute column)
  342. {
  343. var type = TextUtility.Empty;
  344. var length = NumberUtility.Restrict(column.Length, 0, 255).ToString();
  345. switch (column.Type)
  346. {
  347. case ColumnType.Bytes:
  348. type = "blob";
  349. break;
  350. case ColumnType.Integer:
  351. type = "integer";
  352. break;
  353. case ColumnType.Float:
  354. type = "real";
  355. break;
  356. case ColumnType.DateTime:
  357. type = "datetime";
  358. break;
  359. case ColumnType.VarChar:
  360. type = TextUtility.Merge("varchar(", length, ")");
  361. break;
  362. case ColumnType.VarChar191:
  363. type = TextUtility.Merge("varchar(191)");
  364. break;
  365. case ColumnType.VarCharMax:
  366. type = TextUtility.Merge("varchar(255)");
  367. break;
  368. case ColumnType.Text:
  369. type = TextUtility.Merge("text");
  370. break;
  371. case ColumnType.NVarChar:
  372. type = TextUtility.Merge("nvarchar(", length, ")");
  373. break;
  374. case ColumnType.NVarChar191:
  375. type = TextUtility.Merge("nvarchar(191)");
  376. break;
  377. case ColumnType.NVarCharMax:
  378. type = TextUtility.Merge("nvarchar(255)");
  379. break;
  380. case ColumnType.NText:
  381. type = TextUtility.Merge("ntext");
  382. break;
  383. default:
  384. return TextUtility.Empty;
  385. }
  386. return TextUtility.Merge("[", column.Field, "] ", type);
  387. }
  388. /// <summary>创建参数。</summary>
  389. /// <exception cref="ArgumentNullException"></exception>
  390. /// <exception cref="InvalidOperationException"></exception>
  391. public static SQLiteParameter Parameter(Parameter parameter)
  392. {
  393. if (parameter == null) throw new InvalidOperationException("参数无效。");
  394. return Parameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value);
  395. }
  396. /// <summary>创建参数。</summary>
  397. public static SQLiteParameter Parameter(string name, ColumnType type, int size, object value)
  398. {
  399. var n = TextUtility.Trim(name);
  400. if (TextUtility.IsBlank(n)) return null;
  401. var t = GetColumnTypeName(type);
  402. var s = size;
  403. switch (type)
  404. {
  405. case ColumnType.VarChar:
  406. s = NumberUtility.Restrict(s, 0, 8000);
  407. break;
  408. case ColumnType.NVarChar:
  409. s = NumberUtility.Restrict(s, 0, 4000);
  410. break;
  411. case ColumnType.VarChar191:
  412. case ColumnType.NVarChar191:
  413. s = NumberUtility.Restrict(s, 0, 191);
  414. break;
  415. case ColumnType.VarCharMax:
  416. case ColumnType.NVarCharMax:
  417. s = NumberUtility.Restrict(s, 0, 255);
  418. break;
  419. default:
  420. s = 0;
  421. break;
  422. }
  423. var v = value;
  424. if (v is string && v != null && s > 0)
  425. {
  426. v = TextUtility.Left((string)v, s);
  427. }
  428. var p = new SQLiteParameter();
  429. p.ParameterName = n;
  430. p.TypeName = t;
  431. p.Value = v ?? DBNull.Value;
  432. if (s > 0) p.Size = s;
  433. return p;
  434. }
  435. /// <summary>创建参数。</summary>
  436. public static IDbDataParameter Parameter(string field, DbType type, int size, object value)
  437. {
  438. var p = new SQLiteParameter();
  439. p.ParameterName = field;
  440. p.DbType = type;
  441. p.Size = size;
  442. p.Value = value ?? DBNull.Value;
  443. return p;
  444. }
  445. /// <summary>创建参数。</summary>
  446. public static IDbDataParameter Parameter(string field, DbType type, object value)
  447. {
  448. var p = new SQLiteParameter();
  449. p.ParameterName = field;
  450. p.DbType = type;
  451. p.Value = value ?? DBNull.Value;
  452. return p;
  453. }
  454. #endregion
  455. }
  456. }