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.

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