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.

869 lines
36 KiB

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
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
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
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
3 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
3 years ago
4 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
3 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
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 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
3 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
3 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
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Reflection;
  5. using System.Text;
  6. using static Apewer.NumberUtility;
  7. namespace Apewer.Source
  8. {
  9. /// <summary>ORM 帮助程序。</summary>
  10. public static class SourceUtility
  11. {
  12. #region IQuery -> IRecord
  13. /// <summary>读取所有行,生成列表。</summary>
  14. public static T[] Fill<T>(this IQuery query) where T : class, new()
  15. {
  16. var objects = Fill(query, typeof(T));
  17. var array = CollectionUtility.As<object, T>(objects);
  18. return array;
  19. }
  20. /// <summary>读取所有行填充到 T,组成 T[]。</summary>
  21. /// <exception cref="ArgumentNullException"></exception>
  22. /// <exception cref="ArgumentException"></exception>
  23. public static object[] Fill(this IQuery query, Type model)
  24. {
  25. if (query == null) return new object[0];
  26. if (query.Table == null) return new object[0];
  27. if (model == null) return new object[0];
  28. return Fill(query.Table, model);
  29. }
  30. /// <summary>获取指定列的所有值,无效值不加入结果。</summary>
  31. public static T[] Column<T>(this IQuery query, Func<int, T> filler)
  32. {
  33. if (query == null || filler == null) return new T[0];
  34. var rows = query.Rows;
  35. var output = new T[rows];
  36. var added = 0;
  37. for (int r = 0; r < rows; r++)
  38. {
  39. var value = filler(r);
  40. if (value == null) continue;
  41. if (value is string str)
  42. {
  43. if (str == "") continue;
  44. }
  45. output[added] = value;
  46. added++;
  47. }
  48. if (added < 1) return new T[0];
  49. if (added == rows) return output;
  50. var output2 = new T[added];
  51. Array.Copy(output, output2, added);
  52. return output2;
  53. }
  54. /// <summary>将 Query 的行,填充到模型实体。</summary>
  55. /// <remarks>填充失败时返回 NULL 值。</remarks>
  56. /// <exception cref="Exception"></exception>
  57. public static object Row(IQuery query, int rowIndex, Type model, TableStructure structure)
  58. {
  59. // 检查参数。
  60. if (query == null || model == null || structure == null) return null;
  61. if (rowIndex < 0 || rowIndex >= query.Rows) return null;
  62. if (!RuntimeUtility.CanNew(model)) return null;
  63. // 变量别名。
  64. var ts = structure;
  65. var r = rowIndex;
  66. var columns = ts.Columns;
  67. // 检查模型的属性,按属性从表中取相应的列。
  68. var record = Activator.CreateInstance(model);
  69. var properties = model.GetProperties();
  70. foreach (var property in properties)
  71. {
  72. // 在表结构中检查,是否包含此属性,并获取 ColumnAttribute 中的 Field。
  73. var field = null as string;
  74. for (var j = 0; j < columns.Length; j++)
  75. {
  76. if (columns[j].PropertyName == property.Name)
  77. {
  78. field = columns[j].Field;
  79. break;
  80. }
  81. }
  82. if (field == null)
  83. {
  84. if (ts && ts.Table.AllProperties) continue;
  85. field = property.Name;
  86. }
  87. var value = query.Value(r, field);
  88. var setted = Set(record, property, value);
  89. }
  90. return record;
  91. }
  92. static bool Set(object record, PropertyInfo property, object value)
  93. {
  94. // 读取值。
  95. if (value == null) return false;
  96. if (value.Equals(DBNull.Value)) return false;
  97. // 必须有 setter 访问器。
  98. var setter = property.GetSetMethod();
  99. if (setter == null) return false;
  100. // 根据属性类型设置值。
  101. var pt = property.PropertyType;
  102. if (pt.Equals(typeof(object))) setter.Invoke(record, new object[] { value });
  103. else if (pt.Equals(typeof(byte[]))) setter.Invoke(record, new object[] { (byte[])value });
  104. else if (pt.Equals(typeof(string))) setter.Invoke(record, new object[] { value.ToString() });
  105. else if (pt.Equals(typeof(DateTime))) setter.Invoke(record, new object[] { value });
  106. else if (pt.Equals(typeof(bool))) setter.Invoke(record, new object[] { Boolean(value) });
  107. else if (pt.Equals(typeof(byte))) setter.Invoke(record, new object[] { Byte(value) });
  108. else if (pt.Equals(typeof(sbyte))) setter.Invoke(record, new object[] { SByte(value) });
  109. else if (pt.Equals(typeof(short))) setter.Invoke(record, new object[] { Int16(value) });
  110. else if (pt.Equals(typeof(ushort))) setter.Invoke(record, new object[] { UInt16(value) });
  111. else if (pt.Equals(typeof(int))) setter.Invoke(record, new object[] { Int32(value) });
  112. else if (pt.Equals(typeof(uint))) setter.Invoke(record, new object[] { UInt32(value) });
  113. else if (pt.Equals(typeof(long))) setter.Invoke(record, new object[] { Int64(value) });
  114. else if (pt.Equals(typeof(ulong))) setter.Invoke(record, new object[] { UInt64(value) });
  115. else if (pt.Equals(typeof(float))) setter.Invoke(record, new object[] { Single(value) });
  116. else if (pt.Equals(typeof(double))) setter.Invoke(record, new object[] { Double(value) });
  117. else if (pt.Equals(typeof(decimal))) setter.Invoke(record, new object[] { Decimal(value) });
  118. #if !NET20
  119. else if (pt.Equals(typeof(Nullable<DateTime>))) setter.Invoke(record, new object[] { new Nullable<DateTime>((DateTime)value) });
  120. else if (pt.Equals(typeof(Nullable<bool>))) setter.Invoke(record, new object[] { new Nullable<bool>(Boolean(value)) });
  121. else if (pt.Equals(typeof(Nullable<byte>))) setter.Invoke(record, new object[] { new Nullable<byte>(Byte(value)) });
  122. else if (pt.Equals(typeof(Nullable<sbyte>))) setter.Invoke(record, new object[] { new Nullable<sbyte>(SByte(value)) });
  123. else if (pt.Equals(typeof(Nullable<short>))) setter.Invoke(record, new object[] { new Nullable<short>(Int16(value)) });
  124. else if (pt.Equals(typeof(Nullable<ushort>))) setter.Invoke(record, new object[] { new Nullable<int>(UInt16(value)) });
  125. else if (pt.Equals(typeof(Nullable<int>))) setter.Invoke(record, new object[] { new Nullable<int>(Int32(value)) });
  126. else if (pt.Equals(typeof(Nullable<uint>))) setter.Invoke(record, new object[] { new Nullable<uint>(UInt32(value)) });
  127. else if (pt.Equals(typeof(Nullable<long>))) setter.Invoke(record, new object[] { new Nullable<long>(Int64(value)) });
  128. else if (pt.Equals(typeof(Nullable<ulong>))) setter.Invoke(record, new object[] { new Nullable<ulong>(UInt64(value)) });
  129. else if (pt.Equals(typeof(Nullable<float>))) setter.Invoke(record, new object[] { new Nullable<float>(Single(value)) });
  130. else if (pt.Equals(typeof(Nullable<double>))) setter.Invoke(record, new object[] { new Nullable<double>(Double(value)) });
  131. else if (pt.Equals(typeof(Nullable<decimal>))) setter.Invoke(record, new object[] { new Nullable<decimal>(Decimal(value)) });
  132. #endif
  133. else
  134. {
  135. try
  136. {
  137. setter.Invoke(record, new object[] { value });
  138. return true;
  139. }
  140. catch { }
  141. }
  142. return false;
  143. }
  144. #endregion
  145. #region Record
  146. /// <summary>修复记录属性。</summary>
  147. public static void FixProperties(object record)
  148. {
  149. if (record == null) return;
  150. if (record is IRecord key)
  151. {
  152. if (string.IsNullOrEmpty(key.Key)) key.ResetKey();
  153. }
  154. if (record is IRecordMoment moment)
  155. {
  156. var now = moment.GenerateMoment();
  157. if (string.IsNullOrEmpty(moment.Created)) moment.Created = now;
  158. if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now;
  159. }
  160. if (record is IRecordStamp stamp)
  161. {
  162. var now = stamp.GenerateStamp();
  163. if (stamp.Created == 0L) stamp.Created = now;
  164. if (stamp.Updated == 0L) stamp.Updated = now;
  165. }
  166. }
  167. /// <summary>设置 Updated 属性。</summary>
  168. /// <returns>TRUE:设置成功;FALSE:设置失败。</returns>
  169. public static bool SetUpdated(object record)
  170. {
  171. if (record == null) return false;
  172. var setted = false;
  173. if (record is IRecordMoment moment)
  174. {
  175. moment.Updated = moment.GenerateMoment();
  176. setted = true;
  177. }
  178. if (record is IRecordStamp stamp)
  179. {
  180. stamp.Updated = stamp.GenerateStamp();
  181. setted = true;
  182. }
  183. return setted;
  184. }
  185. /// <summary>枚举带有 Table 特性的 <typeparamref name="T"/> 派生类型。</summary>
  186. public static Type[] EnumerateRecords<T>() where T : IRecord => EnumerateRecords(typeof(T));
  187. /// <summary>枚举带有 Table 特性的派生类型。</summary>
  188. /// <exception cref="ArgumentNullException"></exception>
  189. public static Type[] EnumerateRecords(Type baseType)
  190. {
  191. if (baseType == null) throw new ArgumentNullException(nameof(baseType));
  192. var assemblies = AppDomain.CurrentDomain.GetAssemblies();
  193. var builder = new ArrayBuilder<Type>();
  194. foreach (var assembly in assemblies)
  195. {
  196. var types = RuntimeUtility.GetTypes(assembly);
  197. foreach (var type in types)
  198. {
  199. if (!EnumerateRecords(type, baseType)) continue;
  200. if (builder.Contains(type)) continue;
  201. builder.Add(type);
  202. }
  203. }
  204. return builder.Export();
  205. }
  206. static bool EnumerateRecords(Type type, Type @base)
  207. {
  208. if (type == null || @base == null) return false;
  209. if (type.IsAbstract) return false;
  210. if (!RuntimeUtility.Contains<TableAttribute>(type, false)) return false;
  211. if (type.Equals(@base)) return true;
  212. if (RuntimeUtility.IsInherits(type, @base)) return true;
  213. return false;
  214. }
  215. #endregion
  216. #region Query
  217. /// <summary>简单查询:取结果中第 0 列所有单元格的文本形式,可指定查询后关闭服务器连接,返回结果中不包含无效文本。</summary>
  218. /// <param name="source">数据库客户端。</param>
  219. /// <param name="sql">用于查询的 SQL 语句。</param>
  220. /// <exception cref="SqlException"></exception>
  221. public static string[] Column(this IDbAdo source, string sql)
  222. {
  223. if (source == null) return new string[0];
  224. var pool = null as string[];
  225. var rows = 0;
  226. var count = 0;
  227. using (var query = source.Query(sql))
  228. {
  229. if (!query.Success) throw new SqlException(query, sql);
  230. rows = query.Rows;
  231. if (rows < 1) return new string[0];
  232. pool = new string[rows];
  233. for (int i = 0; i < rows; i++)
  234. {
  235. var cell = TextUtility.Trim(query.Text(i));
  236. if (string.IsNullOrEmpty(cell)) continue;
  237. pool[count] = cell;
  238. count++;
  239. }
  240. }
  241. if (count < 1) return new string[0];
  242. var array = new string[count];
  243. Array.Copy(pool, 0, array, 0, count);
  244. return array;
  245. }
  246. /// <summary>简单查询:取结果中第 0 行、第 0 列单元格中的文本,可指定查询后关闭服务器连接。</summary>
  247. /// <param name="dbClient">数据库客户端。</param>
  248. /// <param name="sql">用于查询的 SQL 语句。</param>
  249. /// <exception cref="ArgumentNullException"></exception>
  250. /// <exception cref="SqlException"></exception>
  251. public static string Cell(this IDbAdo dbClient, string sql)
  252. {
  253. if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
  254. if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));
  255. using (var query = dbClient.Query(sql))
  256. {
  257. if (!query.Success) throw new SqlException(query, sql);
  258. var value = TextUtility.Trim(query.Text());
  259. return value;
  260. }
  261. }
  262. /// <summary>查询。</summary>
  263. /// <param name="dbClient">数据库连接。</param>
  264. /// <param name="sql">SQL 语句。</param>
  265. /// <param name="parameters">SQL 参数。</param>
  266. /// <exception cref="ArgumentNullException"></exception>
  267. public static IQuery Query(this IDbClient dbClient, string sql, IEnumerable<KeyValuePair<string, object>> parameters)
  268. {
  269. if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
  270. if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));
  271. var ps = Parameters(dbClient, sql, parameters);
  272. return dbClient.Query(sql, ps);
  273. }
  274. /// <summary>查询。</summary>
  275. /// <param name="dbClient">数据库连接。</param>
  276. /// <param name="sql">SQL 语句。</param>
  277. /// <param name="parameters">参数容器,每个属性表示一个 SQL 参数。此方法将会自动补足参数名称的 @ 前缀。</param>
  278. /// <exception cref="ArgumentNullException"></exception>
  279. public static IQuery Query(this IDbClient dbClient, string sql, object parameters = null)
  280. {
  281. if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
  282. if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));
  283. if (parameters is IEnumerable<KeyValuePair<string, object>> kvps)
  284. {
  285. var ps = Parameters(dbClient, sql, kvps);
  286. return dbClient.Query(sql, ps);
  287. }
  288. {
  289. var ps = ParametersByProperites(dbClient, sql, parameters);
  290. return dbClient.Query(sql, ps);
  291. }
  292. }
  293. #endregion
  294. #region Execute
  295. /// <summary>执行 SQL 语句,并加入参数。</summary>
  296. /// <exception cref="ArgumentNullException"></exception>
  297. public static IExecute Execute(this IDbClient dbClient, string sql, IEnumerable<KeyValuePair<string, object>> parameters, bool autoTransaction = false)
  298. {
  299. if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
  300. if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));
  301. var ps = Parameters(dbClient, sql, parameters);
  302. return dbClient.Execute(sql, ps, autoTransaction);
  303. }
  304. /// <summary>执行 SQL 语句,并加入参数。</summary>
  305. /// <param name="dbClient">数据库连接。</param>
  306. /// <param name="sql">SQL 语句。</param>
  307. /// <param name="parameters">参数容器,每个属性表示一个 SQL 参数。此方法将会自动补足参数名称的 @ 前缀。</param>
  308. /// <param name="autoTransaction">自动使用事务。</param>
  309. /// <exception cref="ArgumentNullException"></exception>
  310. public static IExecute Execute(this IDbClient dbClient, string sql, object parameters = null, bool autoTransaction = false)
  311. {
  312. if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
  313. if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));
  314. if (parameters is IEnumerable<KeyValuePair<string, object>> kvps)
  315. {
  316. var ps = Parameters(dbClient, sql, kvps);
  317. return dbClient.Execute(sql, ps, autoTransaction);
  318. }
  319. {
  320. var ps = ParametersByProperites(dbClient, sql, parameters);
  321. return dbClient.Execute(sql, ps, autoTransaction);
  322. }
  323. }
  324. #endregion
  325. #region Parameter
  326. /// <exception cref="ArgumentNullException"></exception>
  327. static List<IDataParameter> ParametersByProperites(IDbClient dbClient, string sql, object parameters)
  328. {
  329. if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
  330. if (parameters == null) return null;
  331. var lsql = sql.Lower();
  332. var type = parameters.GetType();
  333. var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
  334. var count = properties.Length;
  335. var dict = new Dictionary<string, object>(count);
  336. for (var i = 0; i < count; i++)
  337. {
  338. var property = properties[i];
  339. // 属性必须能够获取值。
  340. var getter = property.GetGetMethod();
  341. if (getter == null) continue;
  342. // 属性值必须有效。
  343. var name = property.Name;
  344. if (name.IsEmpty()) continue;
  345. // 属性不可重复。
  346. if (!name.StartsWith("@")) name = "@" + name;
  347. if (dict.ContainsKey(name)) continue;
  348. // SQL 语句中必须包含此参数。
  349. var lname = name.Lower();
  350. if (!lsql.Contains(lname)) continue;
  351. // 加入字典。
  352. var value = getter.Invoke(parameters, null);
  353. dict.Add(name, value);
  354. }
  355. if (dict.Count < 1) return null;
  356. var ps = new List<IDataParameter>();
  357. foreach (var kvp in dict)
  358. {
  359. var p = dbClient.Parameter(kvp.Key, kvp.Value);
  360. ps.Add(p);
  361. }
  362. return ps;
  363. }
  364. /// <exception cref="ArgumentNullException"></exception>
  365. static List<IDataParameter> Parameters(IDbClient dbClient, string sql, IEnumerable<KeyValuePair<string, object>> parameters)
  366. {
  367. if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
  368. if (parameters == null) return null;
  369. var lsql = sql.Lower();
  370. var names = new List<string>(20);
  371. var ps = new List<IDataParameter>(20);
  372. foreach (var kvp in parameters)
  373. {
  374. var name = kvp.Key;
  375. if (name.IsEmpty()) continue;
  376. // 属性不可重复。
  377. if (!name.StartsWith("@")) name = "@" + name;
  378. if (names.Contains(name)) continue;
  379. // SQL 语句中必须包含此参数。
  380. var lname = name.Lower();
  381. if (!lsql.Contains(lname)) continue;
  382. var p = dbClient.Parameter(name, kvp.Value);
  383. ps.Add(p);
  384. names.Add(name);
  385. }
  386. return ps;
  387. }
  388. #endregion
  389. #region SQL
  390. /// <summary>对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。</summary>
  391. public static string Escape(this string text, int bytes = 0)
  392. {
  393. if (text.IsEmpty()) return "";
  394. var t = text ?? "";
  395. t = t.Replace("\\", "\\\\");
  396. t = t.Replace("'", "\\'");
  397. t = t.Replace("\n", "\\n");
  398. t = t.Replace("\r", "\\r");
  399. t = t.Replace("\b", "\\b");
  400. t = t.Replace("\t", "\\t");
  401. t = t.Replace("\f", "\\f");
  402. if (bytes > 5)
  403. {
  404. if (t.Bytes(Encoding.UTF8).Length > bytes)
  405. {
  406. while (true)
  407. {
  408. t = t.Substring(0, t.Length - 1);
  409. if (t.Bytes(Encoding.UTF8).Length <= (bytes - 4)) break;
  410. }
  411. t = t + " ...";
  412. }
  413. }
  414. return t;
  415. }
  416. /// <summary>限定名称文本,只允许包含字母、数字和下划线。</summary>
  417. public static string SafeName(this string name) => TextUtility.Restrict(name, "0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
  418. #endregion
  419. #region DataTable
  420. /// <summary>解析 DataTable,填充没行到到指定的类型中,形成数组。</summary>
  421. /// <param name="table">将要读取的表。</param>
  422. /// <param name="compatible">当类型不同时,尝试转换以兼容。</param>
  423. /// <returns>由指定类型组成的数组。</returns>
  424. /// <exception cref="ArgumentNullException"></exception>
  425. /// <exception cref="ArgumentException"></exception>
  426. public static T[] Fill<T>(this DataTable table, bool compatible = true)
  427. {
  428. if (table == null) throw new ArgumentNullException(nameof(table), $"参数 {table} 无效。");
  429. var objects = Fill(table, typeof(T), compatible);
  430. var count = objects.Length;
  431. var array = new T[count];
  432. for (var i = 0; i < count; i++) array[i] = (T)objects[i];
  433. return array;
  434. }
  435. /// <summary>解析 DataTable,填充没行到到指定的类型中,形成数组。</summary>
  436. /// <param name="table">将要读取的表。</param>
  437. /// <param name="model">要填充的目标类型,必须是可实例化的引用类型。</param>
  438. /// <param name="compatible">当类型不同时,尝试转换以兼容。</param>
  439. /// <returns>由指定类型组成的数组。</returns>
  440. /// <exception cref="ArgumentNullException"></exception>
  441. /// <exception cref="ArgumentException"></exception>
  442. public static object[] Fill(this DataTable table, Type model, bool compatible = true)
  443. {
  444. if (table == null) throw new ArgumentNullException(nameof(table), $"参数 {table} 无效。");
  445. if (model == null) throw new ArgumentNullException(nameof(model), $"参数 {model} 无效。");
  446. // 检查模型是否允许填充。
  447. var ts = TableStructure.Parse(model, true, true);
  448. if (ts == null) throw new ArgumentException($"无法填充到类型 {model.FullName} 中。");
  449. // 检查行数。
  450. var rows = table.Rows;
  451. var rowsCount = rows.Count;
  452. if (rowsCount < 1) return new object[0];
  453. // 确定数组。
  454. var array = new object[rowsCount];
  455. for (var i = 0; i < rowsCount; i++) array[i] = Activator.CreateInstance(model, true);
  456. // 检查列数。
  457. var columns = table.Columns;
  458. var columnsCount = columns.Count;
  459. if (columnsCount < 1) return array;
  460. // 解析表头,仅保留有名称的列。
  461. var sc = 0;
  462. var sfs = new string[columnsCount];
  463. var sts = new Type[columnsCount];
  464. var sis = new int[columnsCount];
  465. for (var i = 0; i < columnsCount; i++)
  466. {
  467. var column = columns[i];
  468. var key = column.ColumnName.Lower();
  469. if (string.IsNullOrEmpty(key)) continue;
  470. if (sfs.Contains(key)) continue;
  471. sfs[sc] = key;
  472. sts[sc] = column.DataType;
  473. sis[sc] = i;
  474. sc++;
  475. }
  476. if (sc < 1) return array;
  477. // 解析模型列。
  478. var cas = ts.Fillable;
  479. var dc = 0;
  480. var dfs = new string[cas.Length];
  481. var dts = new ColumnAttribute[cas.Length];
  482. for (var i = 0; i < cas.Length; i++)
  483. {
  484. var ca = cas[i];
  485. var key = ca.Field.Lower();
  486. if (string.IsNullOrEmpty(key)) continue;
  487. if (dfs.Contains(key)) continue;
  488. dfs[dc] = key;
  489. dts[dc] = ca;
  490. dc++;
  491. }
  492. if (dc < 1) return array;
  493. // 遍历、填充。
  494. for (var r = 0; r < rowsCount; r++)
  495. {
  496. var record = array[r];
  497. // 遍历 table 的列。
  498. for (var s = 0; s < sc; s++)
  499. {
  500. var sf = sfs[s];
  501. // 遍历 model 的列。
  502. for (var d = 0; d < dc; d++)
  503. {
  504. var df = dfs[d];
  505. if (df != sf) continue;
  506. // 取值、填充。
  507. var value = rows[r][sis[s]];
  508. Fill(record, dts[d], sts[s], value, compatible);
  509. break;
  510. }
  511. }
  512. }
  513. return array;
  514. }
  515. static bool Fill(object record, ColumnAttribute ca, Type st, object value, bool compatible)
  516. {
  517. // 如果是 NULL 则忽略填充。
  518. if (value.IsNull()) return false;
  519. // 获取属性的类型,必须与 table 中的类型相同。
  520. var prop = ca.Property;
  521. if (prop.PropertyType == st)
  522. {
  523. prop.SetValue(record, value, null);
  524. return true;
  525. }
  526. // 类型不同且不需要兼容时,不填充。
  527. if (!compatible) return false;
  528. // 根据属性类型设置值。
  529. var pt = prop.PropertyType;
  530. if (pt.Equals(typeof(object))) prop.SetValue(record, value, null);
  531. else if (pt.Equals(typeof(byte[]))) prop.SetValue(record, (byte[])value, null);
  532. else if (pt.Equals(typeof(string))) prop.SetValue(record, value.ToString(), null);
  533. else if (pt.Equals(typeof(DateTime))) prop.SetValue(record, value, null);
  534. else if (pt.Equals(typeof(bool))) prop.SetValue(record, Boolean(value), null);
  535. else if (pt.Equals(typeof(byte))) prop.SetValue(record, Byte(value), null);
  536. else if (pt.Equals(typeof(sbyte))) prop.SetValue(record, SByte(value), null);
  537. else if (pt.Equals(typeof(short))) prop.SetValue(record, Int16(value), null);
  538. else if (pt.Equals(typeof(ushort))) prop.SetValue(record, UInt16(value), null);
  539. else if (pt.Equals(typeof(int))) prop.SetValue(record, Int32(value), null);
  540. else if (pt.Equals(typeof(uint))) prop.SetValue(record, UInt32(value), null);
  541. else if (pt.Equals(typeof(long))) prop.SetValue(record, Int64(value), null);
  542. else if (pt.Equals(typeof(ulong))) prop.SetValue(record, UInt64(value), null);
  543. else if (pt.Equals(typeof(float))) prop.SetValue(record, Single(value), null);
  544. else if (pt.Equals(typeof(double))) prop.SetValue(record, Double(value), null);
  545. else if (pt.Equals(typeof(decimal))) prop.SetValue(record, Decimal(value), null);
  546. else if (pt.Equals(typeof(Nullable<DateTime>))) prop.SetValue(record, new Nullable<DateTime>((DateTime)value), null);
  547. else if (pt.Equals(typeof(Nullable<bool>))) prop.SetValue(record, new Nullable<bool>(Boolean(value)), null);
  548. else if (pt.Equals(typeof(Nullable<byte>))) prop.SetValue(record, new Nullable<byte>(Byte(value)), null);
  549. else if (pt.Equals(typeof(Nullable<sbyte>))) prop.SetValue(record, new Nullable<sbyte>(SByte(value)), null);
  550. else if (pt.Equals(typeof(Nullable<short>))) prop.SetValue(record, new Nullable<short>(Int16(value)), null);
  551. else if (pt.Equals(typeof(Nullable<ushort>))) prop.SetValue(record, new Nullable<int>(UInt16(value)), null);
  552. else if (pt.Equals(typeof(Nullable<int>))) prop.SetValue(record, new Nullable<int>(Int32(value)), null);
  553. else if (pt.Equals(typeof(Nullable<uint>))) prop.SetValue(record, new Nullable<uint>(UInt32(value)), null);
  554. else if (pt.Equals(typeof(Nullable<long>))) prop.SetValue(record, new Nullable<long>(Int64(value)), null);
  555. else if (pt.Equals(typeof(Nullable<ulong>))) prop.SetValue(record, new Nullable<ulong>(UInt64(value)), null);
  556. else if (pt.Equals(typeof(Nullable<float>))) prop.SetValue(record, new Nullable<float>(Single(value)), null);
  557. else if (pt.Equals(typeof(Nullable<double>))) prop.SetValue(record, new Nullable<double>(Double(value)), null);
  558. else if (pt.Equals(typeof(Nullable<decimal>))) prop.SetValue(record, new Nullable<decimal>(Decimal(value)), null);
  559. else
  560. {
  561. try
  562. {
  563. prop.SetValue(record, value, null);
  564. return true;
  565. }
  566. catch { }
  567. }
  568. return false;
  569. }
  570. /// <summary>将多个实体元素转换为 DataTable。</summary>
  571. /// <typeparam name="T">实体元素的类型。</typeparam>
  572. /// <param name="items">实体元素。</param>
  573. /// <param name="tableName">设置 <see cref="DataTable"/> 的名称。</param>
  574. /// <exception cref="ArgumentNullException"></exception>
  575. /// <exception cref="DuplicateNameException"></exception>
  576. /// <exception cref="InvalidExpressionException"></exception>
  577. public static DataTable DataTable<T>(this IEnumerable<T> items, string tableName = null)
  578. {
  579. if (items == null) throw new ArgumentNullException(nameof(items));
  580. // 解析表结构。
  581. var it = typeof(T);
  582. var ts = TableStructure.Parse(it, true, true);
  583. if (ts == null || ts.Columns == null || ts.Columns.Length < 1)
  584. {
  585. foreach (var item in items)
  586. {
  587. if (item == null) continue;
  588. var itemType = item.GetType();
  589. ts = TableStructure.Parse(itemType, true, true);
  590. if (ts == null) throw new TypeLoadException($"无法解析 {itemType.FullName} 的结构。");
  591. it = itemType;
  592. break;
  593. }
  594. if (ts == null) throw new TypeLoadException($"无法解析 {it.FullName} 的结构。");
  595. }
  596. var cas = ts.Columns;
  597. var width = cas.Length;
  598. if (width < 1) throw new TypeLoadException($"类型 {it.FullName} 的结构中没有列。");
  599. // 初始化列。
  600. var table = new DataTable();
  601. var pis = new PropertyInfo[width];
  602. var fts = new Type[width];
  603. for (var i = 0; i < width; i++)
  604. {
  605. var ca = cas[i];
  606. var pi = ca.Property;
  607. var pt = pi.PropertyType;
  608. pis[i] = pi;
  609. var ft = pt;
  610. if (pt.IsGenericType && pt.GetGenericTypeDefinition() == typeof(Nullable<>))
  611. {
  612. pt.GetGenericArguments();
  613. ft = Nullable.GetUnderlyingType(pt);
  614. }
  615. fts[i] = ft;
  616. var column = new DataColumn(ca.Field, ft);
  617. column.AllowDBNull = true;
  618. table.Columns.Add(column);
  619. }
  620. // 添加行。
  621. foreach (var item in items)
  622. {
  623. if (item == null) continue;
  624. var values = new ArrayBuilder<object>(width);
  625. for (var i = 0; i < width; i++)
  626. {
  627. var value = pis[i].GetValue(item, null);
  628. if (value is DateTime dt)
  629. {
  630. if (dt.Year < 1753)
  631. {
  632. values.Add(DBNull.Value);
  633. continue;
  634. }
  635. }
  636. values.Add(value);
  637. }
  638. table.Rows.Add(values.Export());
  639. }
  640. if (tableName.NotEmpty()) table.TableName = tableName;
  641. else if (ts.TableName.NotEmpty()) table.TableName = ts.TableName;
  642. return table;
  643. }
  644. /// <summary>转换 <see cref="System.Data.DataTable"/> 到 <see cref="ObjectSet{T}"/> 数组,每行记录为一个 ObjectSet 对象。</summary>
  645. /// <returns>当参数 table 无效时返回 0 长度的 <see cref="ObjectSet{T}"/> 数组。</returns>
  646. internal static ObjectSet[] ObjectSet(this DataTable table)
  647. {
  648. if (table == null) return new ObjectSet[0];
  649. var columns = table.Columns.Count;
  650. var fields = new string[columns];
  651. for (var c = 0; c < columns; c++) fields[c] = table.Columns[c].ColumnName;
  652. var rows = table.Rows.Count;
  653. var dicts = new Dictionary<string, object>[rows];
  654. for (var r = 0; r < table.Rows.Count; r++)
  655. {
  656. var dict = new Dictionary<string, object>(columns);
  657. for (var c = 0; c < columns; c++)
  658. {
  659. var field = fields[c];
  660. if (string.IsNullOrEmpty(field)) continue;
  661. if (dict.ContainsKey(field)) continue;
  662. var v = table.Rows[r][c];
  663. if (v.IsNull()) v = null;
  664. dict.Add(field, v);
  665. }
  666. dicts[r] = dict;
  667. }
  668. var oss = new ObjectSet[rows];
  669. for (var i = 0; i < rows; i++) oss[i] = new ObjectSet(dicts[i]);
  670. return oss;
  671. }
  672. internal static string Csv(DataTable table, bool withHead)
  673. {
  674. if (table == null) return null;
  675. var columns = table.Columns.Count;
  676. if (columns < 1) return "";
  677. var sb = new StringBuilder();
  678. if (withHead)
  679. {
  680. for (var c = 0; c < columns; c++)
  681. {
  682. var v = table.Columns[c].ColumnName;
  683. CsvCell(sb, c, v);
  684. }
  685. }
  686. var rows = table.Rows.Count;
  687. for (var r = 0; r < rows; r++)
  688. {
  689. var row = table.Rows[r];
  690. if (withHead || r > 0) sb.Append("\r\n");
  691. for (var c = 0; c < columns; c++) CsvCell(sb, c, row[c]);
  692. }
  693. return sb.ToString();
  694. }
  695. private static void CsvCell(StringBuilder sb, int c, object v)
  696. {
  697. if (c > 0) sb.Append(",");
  698. if (v == null || v.Equals(DBNull.Value)) return;
  699. if (v is bool @bool)
  700. {
  701. sb.Append(@bool ? "TRUE" : "FALSE");
  702. return;
  703. }
  704. if (v is DateTime @datetime)
  705. {
  706. sb.Append(@datetime.Lucid());
  707. return;
  708. }
  709. if (v is byte || v is sbyte || v is short || v is ushort || v is int || v is uint || v is long || v is ulong || v is float || v is double || v is decimal)
  710. {
  711. sb.Append(v.ToString());
  712. return;
  713. }
  714. if (v is char)
  715. {
  716. sb.Append((char)v);
  717. return;
  718. }
  719. var s = (v is string @string) ? @string : v.ToString();
  720. var length = s.Length;
  721. if (length < 1) return;
  722. var quote = false;
  723. var comma = false;
  724. var newline = false;
  725. for (var i = 0; i < length; i++)
  726. {
  727. var @char = s[i];
  728. if (@char == '\"') quote = true;
  729. else if (@char == ',') comma = true;
  730. else if (@char == '\r') newline = false;
  731. else if (@char == '\n') newline = false;
  732. }
  733. if (quote || comma || newline)
  734. {
  735. sb.Append("\"");
  736. s = s.Replace("\"", "\"\"");
  737. sb.Append(s);
  738. sb.Append("\"");
  739. }
  740. else sb.Append(s);
  741. }
  742. #endregion
  743. }
  744. }