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.

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