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.

456 lines
17 KiB

  1. using Apewer.Internals;
  2. using Apewer;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Data;
  6. using System.Reflection;
  7. using System.Text;
  8. namespace Apewer.Source
  9. {
  10. /// <summary></summary>
  11. [Serializable]
  12. public sealed class TableStructure
  13. {
  14. private bool _locked = false;
  15. private string _tablename = Constant.EmptyString;
  16. private bool _independent = false;
  17. private Dictionary<string, ColumnAttribute> _columns = new Dictionary<string, ColumnAttribute>();
  18. internal TableStructure() { }
  19. /// <summary>不依赖 Record 公共属性。</summary>
  20. public bool Independent
  21. {
  22. get { return _independent; }
  23. private set { _independent = value; }
  24. }
  25. /// <summary>表名称。</summary>
  26. public string Table
  27. {
  28. get { return _tablename; }
  29. private set { _tablename = value ?? ""; }
  30. }
  31. /// <summary>列信息。</summary>
  32. public Dictionary<string, ColumnAttribute> Columns
  33. {
  34. get
  35. {
  36. if (_locked)
  37. {
  38. var copy = new Dictionary<string, ColumnAttribute>(_columns.Count);
  39. foreach (var c in _columns) copy.Add(c.Key, c.Value);
  40. return copy;
  41. }
  42. return _columns;
  43. }
  44. private set { _columns = value; }
  45. }
  46. /// <summary>锁定属性,阻止修改。</summary>
  47. public void Lock()
  48. {
  49. _locked = true;
  50. foreach (var c in _columns) c.Value.Lock();
  51. }
  52. #region cache
  53. private static Dictionary<string, TableStructure> _tsc = new Dictionary<string, TableStructure>();
  54. private static Dictionary<string, TableAttribute> _tac = new Dictionary<string, TableAttribute>();
  55. #endregion
  56. #region static
  57. /// <summary></summary>
  58. /// <exception cref="System.Exception"></exception>
  59. /// <exception cref="System.ArgumentNullException"></exception>
  60. public static TableStructure ParseModel(object entity, bool useCache = true)
  61. {
  62. if (entity == null) throw new ArgumentNullException("参数无效");
  63. var type = entity.GetType();
  64. var result = ParseModel(type, useCache);
  65. return result;
  66. }
  67. /// <summary></summary>
  68. /// <exception cref="System.Exception"></exception>
  69. /// <exception cref="System.ArgumentNullException"></exception>
  70. public static TableStructure ParseModel<T>(bool useCache = true) where T : IRecord
  71. {
  72. return ParseModel(typeof(T), useCache);
  73. }
  74. /// <summary></summary>
  75. /// <exception cref="System.Exception"></exception>
  76. /// <exception cref="System.ArgumentNullException"></exception>
  77. public static TableStructure ParseModel(Type model, bool useCache = true)
  78. {
  79. var type = model;
  80. if (type == null) throw new ArgumentNullException("参数无效");
  81. // 使用缓存。
  82. var cacheKey = type.FullName;
  83. if (useCache)
  84. {
  85. var hint = null as TableStructure;
  86. lock (_tsc)
  87. {
  88. if (_tsc.ContainsKey(cacheKey))
  89. {
  90. hint = _tsc[cacheKey];
  91. }
  92. }
  93. if (hint != null) return hint;
  94. }
  95. // 检查基类。
  96. // if (type.BaseType.FullName.Equals(typeof(DatabaseRecord).FullName) == false) return "基类不受支持。";
  97. // 检查 Attribute。
  98. var ta = ParseTable(type);
  99. // 获取所有属性。
  100. var properties = type.GetProperties();
  101. if (properties.LongLength < 1L) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 不包含属性。"));
  102. // Record 根类属性名。
  103. var roots = GetRootProperties();
  104. // 检查字段定义。键:属性名称。
  105. var columns = new Dictionary<string, ColumnAttribute>();
  106. foreach (var property in properties)
  107. {
  108. var ca = ParseColumn(type, property);
  109. if (ca == null) continue;
  110. // 检查冗余。
  111. foreach (var column in columns)
  112. {
  113. if (column.Value.Field == ca.Field)
  114. {
  115. throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 的列名称存在冗余。"));
  116. }
  117. }
  118. // 检查基类。
  119. if (roots.Contains(ca.Property.Name)) ca.Independent = true;
  120. columns.Add(property.Name, ca);
  121. }
  122. // if (columns.Count < 1) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 不包含可用的列。"));
  123. // 排序。
  124. columns = SortColumns(columns);
  125. // 返回结果。
  126. var ts = new TableStructure();
  127. ts.Table = ta.Name;
  128. ts.Independent = ta.Independent;
  129. ts.Columns = columns;
  130. // 锁定属性。
  131. ts.Lock();
  132. // 加入缓存。
  133. if (useCache)
  134. {
  135. lock (_tsc)
  136. {
  137. if (!_tsc.ContainsKey(cacheKey))
  138. {
  139. _tsc.Add(cacheKey, ts);
  140. }
  141. }
  142. }
  143. return ts;
  144. }
  145. /// <summary></summary>
  146. /// <param name="type"></param>
  147. /// <exception cref="Exception"></exception>"
  148. public static TableAttribute ParseTable<T>(bool useCache = true) where T : IRecord
  149. {
  150. return ParseTable(typeof(T), useCache);
  151. }
  152. /// <summary></summary>
  153. /// <param name="type"></param>
  154. /// <exception cref="Exception"></exception>"
  155. public static TableAttribute ParseTable(Type type, bool useCache = true)
  156. {
  157. // 使用缓存。
  158. var cacheKey = type.FullName;
  159. if (useCache)
  160. {
  161. var hint = null as TableAttribute;
  162. lock (_tac)
  163. {
  164. if (_tac.ContainsKey(cacheKey))
  165. {
  166. hint = _tac[cacheKey];
  167. }
  168. }
  169. if (hint != null) return hint;
  170. }
  171. var tas = type.GetCustomAttributes(typeof(TableAttribute), false);
  172. if (tas.LongLength < 1L) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 不包含 ", typeof(TableAttribute).FullName, "。"));
  173. if (tas.LongLength > 1L) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 包含多个 ", typeof(TableAttribute).FullName, "。"));
  174. var ta = (TableAttribute)tas[0];
  175. if (TextVerifier.IsBlank(ta.Name))
  176. {
  177. ta = new TableAttribute("_" + type.Name);
  178. if (TextVerifier.IsBlank(ta.Name)) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 的表名称无效。"));
  179. }
  180. ta.Independent = RuntimeUtility.ContainsAttribute<IndependentAttribute>(type, true);
  181. // 锁定属性。
  182. ta.Lock();
  183. // 加入缓存。
  184. if (useCache)
  185. {
  186. lock (_tac)
  187. {
  188. if (!_tac.ContainsKey(cacheKey))
  189. {
  190. _tac.Add(cacheKey, ta);
  191. }
  192. }
  193. }
  194. return ta;
  195. }
  196. /// <summary></summary>
  197. /// <exception cref="Exception">Exception</exception>"
  198. public static ColumnAttribute ParseColumn(Type type, PropertyInfo property)
  199. {
  200. // 检查 Attributes。
  201. var cas = property.GetCustomAttributes(typeof(ColumnAttribute), false);
  202. if (cas.LongLength < 1L) return null;
  203. if (cas.LongLength > 1L) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 包含多个 ", typeof(ColumnAttribute).FullName, "。"));
  204. var ca = (ColumnAttribute)cas[0];
  205. // 检查列名称。
  206. if (TextVerifier.IsBlank(ca.Field))
  207. {
  208. ca = new ColumnAttribute("_" + property.Name, ca.Type, ca.Length, true);
  209. if (TextVerifier.IsBlank(ca.Field)) throw new Exception(TextGenerator.Merge("类 ", type.FullName, "中,属性 ", property.Name, " 的列名称无效。"));
  210. }
  211. // 检查属性方法。
  212. var getter = property.GetGetMethod(false);
  213. var setter = property.GetSetMethod(false);
  214. if (getter == null) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 不支持获取。"));
  215. if (setter == null) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 不支持设置。"));
  216. // 类型兼容。
  217. var pt = property.PropertyType;
  218. if (pt.Equals(typeof(byte[]).FullName)) ca.Type = ColumnType.Binary;
  219. else if (pt.Equals(typeof(Byte))) ca.Type = ColumnType.Integer;
  220. else if (pt.Equals(typeof(SByte))) ca.Type = ColumnType.Integer;
  221. else if (pt.Equals(typeof(Int16))) ca.Type = ColumnType.Integer;
  222. else if (pt.Equals(typeof(UInt16))) ca.Type = ColumnType.Integer;
  223. else if (pt.Equals(typeof(Int32))) ca.Type = ColumnType.Integer;
  224. else if (pt.Equals(typeof(UInt32))) ca.Type = ColumnType.Integer;
  225. else if (pt.Equals(typeof(Int64))) ca.Type = ColumnType.Integer;
  226. else if (pt.Equals(typeof(Single))) ca.Type = ColumnType.Float;
  227. else if (pt.Equals(typeof(Double))) ca.Type = ColumnType.Float;
  228. else if (pt.Equals(typeof(Decimal))) ca.Type = ColumnType.Float;
  229. else if (pt.Equals(typeof(DateTime))) ca.Type = ColumnType.DateTime;
  230. else if (pt.Equals(typeof(String)))
  231. {
  232. switch (ca.Type)
  233. {
  234. case ColumnType.Binary:
  235. case ColumnType.Integer:
  236. case ColumnType.Float:
  237. case ColumnType.DateTime:
  238. //throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 的类型不受支持。"));
  239. ca.Type = ColumnType.NText;
  240. break;
  241. }
  242. }
  243. else
  244. {
  245. ca.Type = ColumnType.NText;
  246. }
  247. ca.Property = property;
  248. // 锁定属性。
  249. ca.Lock();
  250. return ca;
  251. }
  252. /// <summary>排序。</summary>
  253. public static Dictionary<string, ColumnAttribute> SortColumns(Dictionary<string, ColumnAttribute> columns)
  254. {
  255. // if (type.BaseType.FullName.Equals(typeof(Record).FullName)) // 仅当使用基类时排序。
  256. var sorted = new Dictionary<string, ColumnAttribute>();
  257. if (columns.ContainsKey("Created")) sorted.Add("Created", columns["Created"]);
  258. if (columns.ContainsKey("Updated")) sorted.Add("Updated", columns["Updated"]);
  259. if (columns.ContainsKey("Flag")) sorted.Add("Flag", columns["Flag"]);
  260. if (columns.ContainsKey("Remark")) sorted.Add("Remark", columns["Remark"]);
  261. if (columns.ContainsKey("Key")) sorted.Add("Key", columns["Key"]);
  262. foreach (var property in columns.Keys)
  263. {
  264. if (property == "Created") continue;
  265. if (property == "Updated") continue;
  266. if (property == "Flag") continue;
  267. if (property == "Remark") continue;
  268. if (property == "Key") continue;
  269. sorted.Add(property, columns[property]);
  270. }
  271. return sorted;
  272. }
  273. /// <summary>限定表名称/列名称。</summary>
  274. public static string RestrictName(string name, bool underline)
  275. {
  276. if (name == null || name == Constant.EmptyString) return Constant.EmptyString;
  277. var lower = name.ToLower();
  278. var available = TextGenerator.Merge("_", Constant.NumberCollection, Constant.LowerCollection);
  279. var sb = new StringBuilder();
  280. foreach (var c in lower)
  281. {
  282. if (available.IndexOf(c) >= 0) sb.Append(c);
  283. }
  284. lower = sb.ToString();
  285. if (underline && !lower.StartsWith("_")) lower = TextGenerator.Merge("_", lower);
  286. while (lower.Length > 2 && lower.StartsWith("__")) lower = lower.Substring(1);
  287. if (lower == "_" || lower == Constant.EmptyString) return Constant.EmptyString;
  288. if (lower.Length > 255) lower = lower.Substring(0, 255);
  289. return lower;
  290. }
  291. private static IDataParameter GenerateDataParameter(Record entity, ColumnAttribute attribute, CreateDataParameterCallback callback)
  292. {
  293. var property = attribute.Property;
  294. if (property == null) return null;
  295. var getter = property.GetGetMethod();
  296. if (getter == null) return null;
  297. var parameter = null as IDataParameter;
  298. var value = getter.Invoke(entity, null);
  299. //
  300. if (attribute.Type == ColumnType.Binary || attribute.Type == ColumnType.Integer || attribute.Type == ColumnType.Float)
  301. {
  302. var temp = value;
  303. if (property.PropertyType.FullName == typeof(Decimal).FullName)
  304. {
  305. temp = TextUtility.GetDouble(temp.ToString());
  306. }
  307. parameter = callback(new Parameter(attribute.Field, temp, attribute.Type, attribute.Length));
  308. }
  309. else if (attribute.Type == ColumnType.DateTime)
  310. {
  311. parameter = callback(new Parameter(attribute.Field, value, attribute.Type, 0));
  312. }
  313. else if (property.PropertyType.Equals(typeof(String)))
  314. {
  315. var text = value as string;
  316. if (text == null) text = "";
  317. if (attribute.Length > 0)
  318. {
  319. switch (attribute.Type)
  320. {
  321. case ColumnType.VarChar:
  322. case ColumnType.NVarChar:
  323. text = TextUtility.RestrictLength(text, attribute.Length);
  324. break;
  325. case ColumnType.VarChar255:
  326. case ColumnType.NVarChar255:
  327. text = TextUtility.RestrictLength(text, 255);
  328. break;
  329. }
  330. }
  331. parameter = callback(new Parameter(attribute.Field, text, attribute.Type, attribute.Length));
  332. }
  333. else
  334. {
  335. var text = (value == null) ? TextUtility.EmptyString : value.ToString();
  336. parameter = callback(new Parameter(attribute.Field, text, attribute.Type, attribute.Length));
  337. }
  338. return parameter;
  339. }
  340. /// <summary>生成 IDataParameter 列表,用于 Insert 或 Update。</summary>
  341. /// <exception cref="ArgumentNullException"></exception>
  342. public List<IDataParameter> CreateDataParameters(Record entity, CreateDataParameterCallback callback, params string[] excluded)
  343. {
  344. if (entity == null) throw new ArgumentNullException(nameof(entity));
  345. if (callback == null) throw new ArgumentNullException(nameof(excluded));
  346. entity.FixProperties();
  347. var list = new List<IDataParameter>();
  348. foreach (var column in Columns)
  349. {
  350. var attribute = column.Value;
  351. if (ParseTable(entity.GetType()).Independent && attribute.Independent) continue;
  352. var parameter = GenerateDataParameter(entity, attribute, callback);
  353. if (parameter == null) continue;
  354. var add = true;
  355. foreach (var exclude in excluded)
  356. {
  357. var lower = parameter.ParameterName.ToLower();
  358. if (lower == exclude.ToLower())
  359. {
  360. add = false;
  361. }
  362. }
  363. if (add) list.Add(parameter);
  364. }
  365. return list;
  366. }
  367. #endregion
  368. #region
  369. /// <summary>获取 Record 根类中的属性名称。</summary>
  370. private static List<string> GetRootProperties()
  371. {
  372. var list = new List<string>();
  373. var type = typeof(Record);
  374. var properties = type.GetProperties();
  375. foreach (var property in properties)
  376. {
  377. if (RuntimeUtility.ContainsAttribute<ColumnAttribute>(property, false))
  378. {
  379. list.Add(property.Name);
  380. }
  381. }
  382. return list;
  383. }
  384. #endregion
  385. }
  386. }