using System; using System.Collections.Generic; using System.Data; using System.Reflection; using System.Text; namespace Apewer.Source { /// ORM 帮助程序。 public static class SourceUtility { #region ORM /// 读取所有行,生成列表。 public static T[] Fill(this IQuery query) where T : class, new() { var objects = Fill(query, typeof(T)); var array = CollectionUtility.As(objects); return array; } /// 读取所有行填充到 T,组成 T[]。 /// /// public static object[] Fill(this IQuery query, Type model) { if (query == null) return new object[0]; if (query.Table == null) return new object[0]; if (model == null) return new object[0]; return Fill(query.Table, model); } /// 将 Query 的行,填充到模型实体。 /// 填充失败时返回 NULL 值。 /// public static object FillRow(IQuery query, int rowIndex, Type model, TableStructure structure) => FillRow(query?.Table, rowIndex, model, structure); /// 将 Query 的行,填充到模型实体。 /// 填充失败时返回 NULL 值。 /// public static object FillRow(DataTable table, int rowIndex, Type model, TableStructure structure) { // 检查参数。 if (table == null || model == null || structure == null) return null; if (rowIndex < 0 || rowIndex >= table.Rows.Count) return null; if (!RuntimeUtility.CanNew(model)) return null; // 变量别名。 var ts = structure; var r = rowIndex; var columns = ts.Columns; // 检查模型的属性,按属性从表中取相应的列。 var record = Activator.CreateInstance(model); var properties = model.GetProperties(); foreach (var property in properties) { // 在表结构中检查,是否包含此属性,并获取 ColumnAttribute 中的 Field。 var field = null as string; for (var j = 0; j < columns.Length; j++) { if (columns[j].PropertyName == property.Name) { field = columns[j].Field; break; } } if (field == null) { if (ts && ts.Table.AllProperties) continue; field = property.Name; } var value = table.Rows[r][field]; if (value != null && value.Equals(DBNull.Value)) value = null; var setted = Set(record, property, value); } return record; } static bool Set(object record, PropertyInfo property, object value) { // 读取值。 if (value == null) return false; if (value.Equals(DBNull.Value)) return false; // 必须有 setter 访问器。 var setter = property.GetSetMethod(); if (setter == null) return false; // 根据属性类型设置值。 var pt = property.PropertyType; if (pt.Equals(typeof(object))) setter.Invoke(record, new object[] { value }); else if (pt.Equals(typeof(byte[]))) setter.Invoke(record, new object[] { (byte[])value }); else if (pt.Equals(typeof(string))) setter.Invoke(record, new object[] { value.ToString() }); else if (pt.Equals(typeof(DateTime))) setter.Invoke(record, new object[] { value }); else if (pt.Equals(typeof(bool))) setter.Invoke(record, new object[] { NumberUtility.Boolean(value) }); else if (pt.Equals(typeof(byte))) setter.Invoke(record, new object[] { NumberUtility.Byte(value) }); else if (pt.Equals(typeof(sbyte))) setter.Invoke(record, new object[] { NumberUtility.SByte(value) }); else if (pt.Equals(typeof(short))) setter.Invoke(record, new object[] { NumberUtility.Int16(value) }); else if (pt.Equals(typeof(ushort))) setter.Invoke(record, new object[] { NumberUtility.UInt16(value) }); else if (pt.Equals(typeof(int))) setter.Invoke(record, new object[] { NumberUtility.Int32(value) }); else if (pt.Equals(typeof(uint))) setter.Invoke(record, new object[] { NumberUtility.UInt32(value) }); else if (pt.Equals(typeof(long))) setter.Invoke(record, new object[] { NumberUtility.Int64(value) }); else if (pt.Equals(typeof(ulong))) setter.Invoke(record, new object[] { NumberUtility.UInt64(value) }); else if (pt.Equals(typeof(float))) setter.Invoke(record, new object[] { NumberUtility.Single(value) }); else if (pt.Equals(typeof(double))) setter.Invoke(record, new object[] { NumberUtility.Double(value) }); else if (pt.Equals(typeof(decimal))) setter.Invoke(record, new object[] { NumberUtility.Decimal(value) }); #if !NET20 else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable((DateTime)value) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Boolean(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Byte(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.SByte(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Int16(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.UInt16(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Int32(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.UInt32(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Int64(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.UInt64(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Single(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Double(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Decimal(value)) }); #endif else { try { setter.Invoke(record, new object[] { value }); return true; } catch { } } return false; } /// 解析 DataTable,填充没行到到指定的类型中,形成数组。 /// 将要读取的表。 /// 当类型不同时,尝试转换以兼容。 /// 由指定类型组成的数组。 /// /// public static T[] Fill(this DataTable table, bool compatible = true) { if (table == null) throw new ArgumentNullException(nameof(table), $"参数 {table} 无效。"); var objects = Fill(table, typeof(T), compatible); var count = objects.Length; var array = new T[count]; for (var i = 0; i < count; i++) array[i] = (T)objects[i]; return array; } /// 解析 DataTable,填充没行到到指定的类型中,形成数组。 /// 将要读取的表。 /// 要填充的目标类型,必须是可实例化的引用类型。 /// 当类型不同时,尝试转换以兼容。 /// 由指定类型组成的数组。 /// /// public static object[] Fill(this DataTable table, Type model, bool compatible = true) { if (table == null) throw new ArgumentNullException(nameof(table), $"参数 {table} 无效。"); if (model == null) throw new ArgumentNullException(nameof(model), $"参数 {model} 无效。"); // 检查模型是否允许填充。 var ts = TableStructure.Parse(model, true, true); if (ts == null) throw new ArgumentException($"无法填充到类型 {model.FullName} 中。"); // 检查行数。 var rows = table.Rows; var rowsCount = rows.Count; if (rowsCount < 1) return new object[0]; // 确定数组。 var array = new object[rowsCount]; for (var i = 0; i < rowsCount; i++) array[i] = Activator.CreateInstance(model, true); // 检查列数。 var columns = table.Columns; var columnsCount = columns.Count; if (columnsCount < 1) return array; // 解析表头,仅保留有名称的列。 var sc = 0; var sfs = new string[columnsCount]; var sts = new Type[columnsCount]; var sis = new int[columnsCount]; for (var i = 0; i < columnsCount; i++) { var column = columns[i]; var key = column.ColumnName.Lower(); if (string.IsNullOrEmpty(key)) continue; if (sfs.Contains(key)) continue; sfs[sc] = key; sts[sc] = column.DataType; sis[sc] = i; sc++; } if (sc < 1) return array; // 解析模型列。 var cas = ts.Fillable; var dc = 0; var dfs = new string[cas.Length]; var dts = new ColumnAttribute[cas.Length]; for (var i = 0; i < cas.Length; i++) { var ca = cas[i]; var key = ca.Field.Lower(); if (string.IsNullOrEmpty(key)) continue; if (dfs.Contains(key)) continue; dfs[dc] = key; dts[dc] = ca; dc++; } if (dc < 1) return array; // 遍历、填充。 for (var r = 0; r < rowsCount; r++) { var record = array[r]; // 遍历 table 的列。 for (var s = 0; s < sc; s++) { var sf = sfs[s]; // 遍历 model 的列。 for (var d = 0; d < dc; d++) { var df = dfs[d]; if (df != sf) continue; // 取值、填充。 var value = rows[r][sis[s]]; Fill(record, dts[d], sts[s], value, compatible); break; } } } return array; } static bool Fill(object record, ColumnAttribute ca, Type st, object value, bool compatible) { // 如果是 NULL 则忽略填充。 if (value.IsNull()) return false; // 获取属性的类型,必须与 table 中的类型相同。 var prop = ca.Property; if (prop.PropertyType == st) { prop.SetValue(record, value, null); return true; } // 类型不同且不需要兼容时,不填充。 if (!compatible) return false; // 根据属性类型设置值。 var pt = prop.PropertyType; if (pt.Equals(typeof(object))) prop.SetValue(record, value, null); else if (pt.Equals(typeof(byte[]))) prop.SetValue(record, (byte[])value, null); else if (pt.Equals(typeof(string))) prop.SetValue(record, value.ToString(), null); else if (pt.Equals(typeof(DateTime))) prop.SetValue(record, value, null); else if (pt.Equals(typeof(bool))) prop.SetValue(record, NumberUtility.Boolean(value), null); else if (pt.Equals(typeof(byte))) prop.SetValue(record, NumberUtility.Byte(value), null); else if (pt.Equals(typeof(sbyte))) prop.SetValue(record, NumberUtility.SByte(value), null); else if (pt.Equals(typeof(short))) prop.SetValue(record, NumberUtility.Int16(value), null); else if (pt.Equals(typeof(ushort))) prop.SetValue(record, NumberUtility.UInt16(value), null); else if (pt.Equals(typeof(int))) prop.SetValue(record, NumberUtility.Int32(value), null); else if (pt.Equals(typeof(uint))) prop.SetValue(record, NumberUtility.UInt32(value), null); else if (pt.Equals(typeof(long))) prop.SetValue(record, NumberUtility.Int64(value), null); else if (pt.Equals(typeof(ulong))) prop.SetValue(record, NumberUtility.UInt64(value), null); else if (pt.Equals(typeof(float))) prop.SetValue(record, NumberUtility.Single(value), null); else if (pt.Equals(typeof(double))) prop.SetValue(record, NumberUtility.Double(value), null); else if (pt.Equals(typeof(decimal))) prop.SetValue(record, NumberUtility.Decimal(value), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable((DateTime)value), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Boolean(value)), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Byte(value)), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.SByte(value)), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Int16(value)), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.UInt16(value)), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Int32(value)), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.UInt32(value)), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Int64(value)), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.UInt64(value)), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Single(value)), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Double(value)), null); else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Decimal(value)), null); else { try { prop.SetValue(record, value, null); return true; } catch { } } return false; } #endregion #region Record /// 修复记录属性。 public static void FixProperties(object record) { if (record == null) return; if (record is IRecord key) { if (string.IsNullOrEmpty(key.Key)) key.ResetKey(); } if (record is IRecordMoment moment) { var now = moment.GenerateMoment(); if (string.IsNullOrEmpty(moment.Created)) moment.Created = now; if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now; } if (record is IRecordStamp stamp) { var now = stamp.GenerateStamp(); if (stamp.Created == 0L) stamp.Created = now; if (stamp.Updated == 0L) stamp.Updated = now; } } /// 设置 Updated 属性。 /// TRUE:设置成功;FALSE:设置失败。 public static bool SetUpdated(object record) { if (record == null) return false; var setted = false; if (record is IRecordMoment moment) { moment.Updated = moment.GenerateMoment(); setted = true; } if (record is IRecordStamp stamp) { stamp.Updated = stamp.GenerateStamp(); setted = true; } return setted; } /// 枚举带有 Table 特性的 派生类型。 public static Type[] EnumerateRecords() where T : IRecord => EnumerateRecords(typeof(T)); /// 枚举带有 Table 特性的派生类型。 /// public static Type[] EnumerateRecords(Type baseType) { if (baseType == null) throw new ArgumentNullException(nameof(baseType)); var assemblies = AppDomain.CurrentDomain.GetAssemblies(); var builder = new ArrayBuilder(); foreach (var assembly in assemblies) { var types = RuntimeUtility.GetTypes(assembly); foreach (var type in types) { if (!EnumerateRecords(type, baseType)) continue; if (builder.Contains(type)) continue; builder.Add(type); } } return builder.Export(); } static bool EnumerateRecords(Type type, Type @base) { if (type == null || @base == null) return false; if (type.IsAbstract) return false; if (!RuntimeUtility.Contains(type, false)) return false; if (type.Equals(@base)) return true; if (RuntimeUtility.IsInherits(type, @base)) return true; return false; } #endregion #region Query /// 简单查询:取结果中第 0 列所有单元格的文本形式,可指定查询后关闭服务器连接,返回结果中不包含无效文本。 /// 数据库客户端。 /// 用于查询的 SQL 语句。 /// public static string[] Column(this IDbAdo source, string sql) { if (source == null) return new string[0]; var pool = null as string[]; var rows = 0; var count = 0; using (var query = source.Query(sql)) { if (!query.Success) throw new SqlException(query, sql); rows = query.Rows; if (rows < 1) return new string[0]; pool = new string[rows]; for (int i = 0; i < rows; i++) { var cell = TextUtility.Trim(query.Text(i, 0)); if (string.IsNullOrEmpty(cell)) continue; pool[count] = cell; count++; } } if (count < 1) return new string[0]; if (count == rows) return pool; var array = new string[count]; Array.Copy(pool, 0, array, 0, count); return array; } /// 简单查询:取结果中第 0 行、第 0 列单元格中的文本,可指定查询后关闭服务器连接。 /// 数据库客户端。 /// 用于查询的 SQL 语句。 /// /// public static string Cell(this IDbAdo dbClient, string sql) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql)); using (var query = dbClient.Query(sql)) { if (!query.Success) throw new SqlException(query, sql); var value = TextUtility.Trim(query.Text(0, 0)); return value; } } /// 查询。 /// 数据库连接。 /// SQL 语句。 /// SQL 参数。 /// public static IQuery Query(this IDbAdo dbClient, string sql, IEnumerable> parameters) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql)); var ps = Parameters(dbClient, sql, parameters); return dbClient.Query(sql, ps); } /// 查询。 /// 数据库连接。 /// SQL 语句。 /// 参数容器,每个属性表示一个 SQL 参数。此方法将会自动补足参数名称的 @ 前缀。 /// public static IQuery Query(this IDbAdo dbClient, string sql, object parameters = null) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql)); if (parameters is IEnumerable> kvps) { var ps = Parameters(dbClient, sql, kvps); return dbClient.Query(sql, ps); } { var ps = ParametersByProperites(dbClient, sql, parameters); return dbClient.Query(sql, ps); } } /// 执行 SELECT 语句,获取查询结果。 /// 数据库连接。 /// 事务。 /// SQL 语句。 /// 参数。 /// 超时秒数。 /// 查询结果。 public static DataTable Query(this IDbConnection connection, IDbTransaction transaction, string sql, IEnumerable parameters = null, int timeout = 3600) { if (connection == null) throw new ArgumentNullException(nameof(connection)); if (string.IsNullOrEmpty(sql)) throw new ArgumentNullException(nameof(sql)); if (connection.State != ConnectionState.Open) connection.Open(); using (var command = connection.CreateCommand()) { if (transaction != null) command.Transaction = transaction; if (timeout > 0) command.CommandTimeout = timeout; command.CommandText = sql; if (parameters != null) { foreach (var parameter in parameters) { if (parameter == null) continue; command.Parameters.Add(parameter); } } using (var reader = command.ExecuteReader()) { var table = new DataTable(); table.Load(reader); return table; } } } #endregion #region Execute /// 执行 SQL 语句,并加入参数。 /// public static IExecute Execute(this IDbAdo dbClient, string sql, IEnumerable> parameters, bool autoTransaction = false) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql)); var ps = Parameters(dbClient, sql, parameters); return dbClient.Execute(sql, ps, autoTransaction); } /// 执行 SQL 语句,并加入参数。 /// 数据库连接。 /// SQL 语句。 /// 参数容器,每个属性表示一个 SQL 参数。此方法将会自动补足参数名称的 @ 前缀。 /// 自动使用事务。 /// public static IExecute Execute(this IDbAdo dbClient, string sql, object parameters = null, bool autoTransaction = false) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql)); if (parameters is IEnumerable> kvps) { var ps = Parameters(dbClient, sql, kvps); return dbClient.Execute(sql, ps, autoTransaction); } { var ps = ParametersByProperites(dbClient, sql, parameters); return dbClient.Execute(sql, ps, autoTransaction); } } /// 执行 SQL 语句,获取影响的行数。 /// 数据库连接。 /// 事务。 /// SQL 语句。 /// 参数。 /// 超时秒数。 /// 行数。 public static int Execute(this IDbConnection connection, IDbTransaction transaction, string sql, IEnumerable parameters = null, int timeout = 3600) { if (connection == null) throw new ArgumentNullException(nameof(connection)); if (string.IsNullOrEmpty(sql)) throw new ArgumentNullException(nameof(sql)); if (connection.State != ConnectionState.Open) connection.Open(); using (var command = connection.CreateCommand()) { if (transaction != null) command.Transaction = transaction; if (timeout > 0) command.CommandTimeout = timeout; command.CommandText = sql; if (parameters != null) { foreach (var parameter in parameters) { if (parameter == null) continue; command.Parameters.Add(parameter); } } var rows = command.ExecuteNonQuery(); return rows; } } #endregion #region Transaction /// 启动事务,执行指定的过程并在完成后提交事务。若过程被异常打断,则回滚事务。 /// /// public static void InTransaction(this IDbAdo source, Action action) { // 检查参数。 if (source == null) throw new ArgumentNullException(nameof(source), "数据源无效。"); if (action == null) throw new ArgumentNullException(nameof(action), "没有指定要在事物中执行的程序。"); InTransaction(source, () => { action.Invoke(); return null; }); } /// 启动事务,执行指定的过程并在完成后提交事务。若过程被异常打断,则回滚事务。 /// /// public static T InTransaction(this IDbAdo source, Func func) { // 检查参数。 if (source == null) throw new ArgumentNullException(nameof(source), "数据源无效。"); if (func == null) throw new ArgumentNullException(nameof(func), "没有指定要在事物中执行的程序。"); // 已经存在事务。 if (source.Transaction != null) return func.Invoke(); // 启动事务。 var begin = source.Begin(); if (begin.NotEmpty()) throw new SqlException("无法启动事务:" + begin); var result = default(T); var success = false; try { // 在事务内运行。 result = func.Invoke(); success = true; } finally { if (success) { // 执行成功,提交事务。 var commit = source.Commit(); if (!string.IsNullOrEmpty(commit)) throw new SqlException(commit); } else { // 执行失败,回滚事务。 try { source.Rollback(); } catch { } } } return result; } #endregion #region Parameter /// static List ParametersByProperites(IDbAdo dbClient, string sql, object parameters) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); if (parameters == null) return null; var lsql = sql.Lower(); var type = parameters.GetType(); var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); var count = properties.Length; var dict = new Dictionary(count); for (var i = 0; i < count; i++) { var property = properties[i]; // 属性必须能够获取值。 var getter = property.GetGetMethod(); if (getter == null) continue; // 属性值必须有效。 var name = property.Name; if (name.IsEmpty()) continue; // 属性不可重复。 if (!name.StartsWith("@")) name = "@" + name; if (dict.ContainsKey(name)) continue; // SQL 语句中必须包含此参数。 var lname = name.Lower(); if (!lsql.Contains(lname)) continue; // 加入字典。 var value = getter.Invoke(parameters, null); dict.Add(name, value); } if (dict.Count < 1) return null; var ps = new List(); foreach (var kvp in dict) { var p = dbClient.Parameter(kvp.Key, kvp.Value); ps.Add(p); } return ps; } /// static List Parameters(IDbAdo dbClient, string sql, IEnumerable> parameters) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); if (parameters == null) return null; var lsql = sql.Lower(); var names = new List(20); var ps = new List(20); foreach (var kvp in parameters) { var name = kvp.Key; if (name.IsEmpty()) continue; // 属性不可重复。 if (!name.StartsWith("@")) name = "@" + name; if (names.Contains(name)) continue; // SQL 语句中必须包含此参数。 var lname = name.Lower(); if (!lsql.Contains(lname)) continue; var p = dbClient.Parameter(name, kvp.Value); ps.Add(p); names.Add(name); } return ps; } #endregion #region SQL /// 对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。 public static string Escape(this string text, int bytes = 0) { if (text.IsEmpty()) return ""; var t = text ?? ""; t = t.Replace("\\", "\\\\"); t = t.Replace("'", "\\'"); t = t.Replace("\n", "\\n"); t = t.Replace("\r", "\\r"); t = t.Replace("\b", "\\b"); t = t.Replace("\t", "\\t"); t = t.Replace("\f", "\\f"); if (bytes > 5) { if (t.Bytes(Encoding.UTF8).Length > bytes) { while (true) { t = t.Substring(0, t.Length - 1); if (t.Bytes(Encoding.UTF8).Length <= (bytes - 4)) break; } t = t + " ..."; } } return t; } /// 限定名称文本,只允许包含字母、数字和下划线。 public static string SafeName(this string name) => TextUtility.Restrict(name, "0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); #endregion #region 数据模型 -> DataTable /// 将多个实体元素转换为 DataTable。 /// 实体元素的类型。 /// 实体元素。 /// 设置 的名称。 /// /// /// public static DataTable DataTable(this IEnumerable items, string tableName = null) { if (items == null) throw new ArgumentNullException(nameof(items)); // 解析表结构。 var it = typeof(T); var ts = TableStructure.Parse(it, true, true); if (ts == null || ts.Columns == null || ts.Columns.Length < 1) { foreach (var item in items) { if (item == null) continue; var itemType = item.GetType(); ts = TableStructure.Parse(itemType, true, true); if (ts == null) throw new TypeLoadException($"无法解析 {itemType.FullName} 的结构。"); it = itemType; break; } if (ts == null) throw new TypeLoadException($"无法解析 {it.FullName} 的结构。"); } var cas = ts.Columns; var width = cas.Length; if (width < 1) throw new TypeLoadException($"类型 {it.FullName} 的结构中没有列。"); // 初始化列。 var table = new DataTable(); var pis = new PropertyInfo[width]; var fts = new Type[width]; for (var i = 0; i < width; i++) { var ca = cas[i]; var pi = ca.Property; var pt = pi.PropertyType; pis[i] = pi; var ft = pt; if (pt.IsGenericType && pt.GetGenericTypeDefinition() == typeof(Nullable<>)) { pt.GetGenericArguments(); ft = Nullable.GetUnderlyingType(pt); } fts[i] = ft; var column = new DataColumn(ca.Field, ft); column.AllowDBNull = true; table.Columns.Add(column); } // 添加行。 foreach (var item in items) { if (item == null) continue; var values = new ArrayBuilder(width); for (var i = 0; i < width; i++) { var value = pis[i].GetValue(item, null); if (value is DateTime dt) { if (dt.Year < 1753) { values.Add(DBNull.Value); continue; } } values.Add(value); } table.Rows.Add(values.Export()); } if (tableName.NotEmpty()) table.TableName = tableName; else if (ts.TableName.NotEmpty()) table.TableName = ts.TableName; return table; } #endregion #region DataTable 序列化 /// 转换 数组,每行记录为一个 ObjectSet 对象。 /// 当参数 table 无效时返回 0 长度的 数组。 public static ObjectSet[] ObjectSet(this DataTable table) { if (table == null) return new ObjectSet[0]; var columns = table.Columns.Count; var fields = new string[columns]; for (var c = 0; c < columns; c++) fields[c] = table.Columns[c].ColumnName; var rows = table.Rows.Count; var dicts = new Dictionary[rows]; for (var r = 0; r < table.Rows.Count; r++) { var dict = new Dictionary(columns); for (var c = 0; c < columns; c++) { var field = fields[c]; if (string.IsNullOrEmpty(field)) continue; if (dict.ContainsKey(field)) continue; var v = table.Rows[r][c]; if (v.IsNull()) v = null; dict.Add(field, v); } dicts[r] = dict; } var oss = new ObjectSet[rows]; for (var i = 0; i < rows; i++) oss[i] = new ObjectSet(dicts[i]); return oss; } /// 转换为 Json 对象。 public static Json ToJson(this DataTable table, Func dateTimeFormatter = null) { if (table == null) return null; var columns = ToJson(table.Columns); var rows = ToJson(table.Rows, dateTimeFormatter); var jsonObject = Json.NewObject(); jsonObject.SetProperty("columns", columns); jsonObject.SetProperty("rows", rows); return jsonObject; } /// 转换为 Json 对象。 public static Json ToJson(this DataColumnCollection columns) { if (columns == null) return null; var json = Json.NewArray(); var count = columns.Count; for (var c = 0; c < count; c++) { var dc = columns[c]; var column = Json.NewObject(); column.SetProperty("name", dc.ColumnName); column.SetProperty("type", dc.DataType.FullName); json.AddItem(column); } return json; } /// 转换为 Json 对象。 public static Json ToJson(this DataRowCollection rows, Func dateTimeFormatter = null) { if (rows == null) return null; var json = Json.NewArray(); var count = rows.Count; for (var r = 0; r < count; r++) { json.AddItem(ToJson(rows[r], dateTimeFormatter)); } return json; } /// 转换为 Json 对象。 public static Json ToJson(this DataRow row, Func dateTimeFormatter = null) { if (row == null) return null; var cells = row.ItemArray; var count = cells.Length; var json = Json.NewArray(); for (var c = 0; c < count; c++) { var value = cells[c]; if (value == null || value.Equals(DBNull.Value)) { json.AddItem(); continue; } if (value is DateTime vDateTime) { if (dateTimeFormatter == null) { json.AddItem(Json.SerializeDateTime(vDateTime)); continue; } else { value = dateTimeFormatter.Invoke(vDateTime); if (value == null || value.Equals(DBNull.Value)) { json.AddItem(); continue; } } } if (value is string @string) json.AddItem(@string); else if (value is byte @byte) json.AddItem(@byte); else if (value is short @short) json.AddItem(@short); else if (value is int @int) json.AddItem(@int); else if (value is long @long) json.AddItem(@long); else if (value is float @float) json.AddItem(@float); else if (value is double @double) json.AddItem(@double); else if (value is decimal @decimal) json.AddItem(@decimal); else if (value is bool @bool) json.AddItem(@bool); else if (value is byte[] bytes) json.AddItem(bytes.Base64()); else json.AddItem(TextUtility.Text(value)); } return json; } /// 转换 为 CSV 文本,不存在表时返回 NULL 值。可指定是否包含表头。 public static string Csv(DataTable table, bool withHead = false) { if (table == null) return null; var columns = table.Columns.Count; if (columns < 1) return ""; var sb = new StringBuilder(); if (withHead) { for (var c = 0; c < columns; c++) { var v = table.Columns[c].ColumnName; CsvCell(sb, c, v); } } var rows = table.Rows.Count; for (var r = 0; r < rows; r++) { var row = table.Rows[r]; if (withHead || r > 0) sb.Append("\r\n"); for (var c = 0; c < columns; c++) CsvCell(sb, c, row[c]); } return sb.ToString(); } private static void CsvCell(StringBuilder sb, int c, object v) { if (c > 0) sb.Append(","); if (v == null || v.Equals(DBNull.Value)) return; if (v is bool @bool) { sb.Append(@bool ? "TRUE" : "FALSE"); return; } if (v is DateTime @datetime) { sb.Append(@datetime.Lucid()); return; } 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) { sb.Append(v.ToString()); return; } if (v is char) { sb.Append((char)v); return; } var s = (v is string @string) ? @string : v.ToString(); var length = s.Length; if (length < 1) return; var quote = false; var comma = false; var newline = false; for (var i = 0; i < length; i++) { var @char = s[i]; if (@char == '\"') quote = true; else if (@char == ',') comma = true; else if (@char == '\r') newline = false; else if (@char == '\n') newline = false; } if (quote || comma || newline) { sb.Append("\""); s = s.Replace("\"", "\"\""); sb.Append(s); sb.Append("\""); } else sb.Append(s); } #endregion #region DataTable 快捷操作 /// 获取默认表中指定单元格的内容。 /// 数据表。 /// 行索引,从 0 开始。 /// 列索引,从 0 开始。 public static object Value(this DataTable table, int rowIndex, int columnIndex) { if (table != null) { if (rowIndex >= 0 && rowIndex < table.Rows.Count) { if (columnIndex >= 0 && columnIndex < table.Columns.Count) { var value = table.Rows[rowIndex][columnIndex]; if (value == null || value.Equals(DBNull.Value)) return null; return value; } } } return null; } /// 获取默认表中指定单元的内容。 /// 数据表。 /// 行索引,从 0 开始。 /// 列名称/字段名称,此名称不区分大小写。 public static object Value(this DataTable table, int rowIndex, string columnName) { if (table != null && !string.IsNullOrEmpty(columnName)) { if ((rowIndex < table.Rows.Count) && (rowIndex >= 0)) { try { var value = table.Rows[rowIndex][columnName]; if (value == null || value.Equals(DBNull.Value)) return null; return value; } catch { } } } return null; } /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 public static Class DateTime(this DataTable table, int row, int column) => table == null ? null : ClockUtility.DateTime(table.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行开始。 public static Class DateTime(this DataTable table, int row, string column) => table == null ? null : ClockUtility.DateTime(table.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 public static Int32 Int32(this DataTable table, int row, int column) => table == null ? 0 : NumberUtility.Int32(table.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行开始。 public static Int32 Int32(this DataTable table, int row, string column) => table == null ? 0 : NumberUtility.Int32(table.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 public static Int64 Int64(this DataTable table, int row, int column) => table == null ? 0L : NumberUtility.Int64(table.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行开始。 public static Int64 Int64(this DataTable table, int row, string column) => table == null ? 0L : NumberUtility.Int64(table.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 public static Decimal Decimal(this DataTable table, int row, int column) => table == null ? 0M : NumberUtility.Decimal(table.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行开始。 public static Decimal Decimal(this DataTable table, int row, string column) => table == null ? 0M : NumberUtility.Decimal(table.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。> public static Double Double(this DataTable table, int row, int column) => table == null ? 0D : NumberUtility.Double(table.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行开始。> public static Double Double(this DataTable table, int row, string column) => table == null ? 0D : NumberUtility.Double(table.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 public static string Text(this DataTable table, int row, int column) => table == null ? null : TextUtility.Text(table.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行开始。 public static string Text(this DataTable table, int row, string column) => table == null ? null : TextUtility.Text(table.Value(row, column)); #endregion #region Dynamic #if NET40_OR_GREATER /// 转换 ObjectSet 数组为 dynamic 数组。 public static dynamic[] Dynamic(this ObjectSet[] oss) { if (oss == null) return new dynamic[0]; var eos = oss.Expando(); var ds = new dynamic[eos.Length]; eos.CopyTo(ds, 0); return ds; } #endif #endregion #region 表达式计算 /// 计算文本表达式。 public static object Compute(string expression) { using (var table = new DataTable()) { var result = table.Compute(expression, null); if (result.IsNull()) return null; return result; } } #endregion } }