|
|
using System; using System.Collections.Generic; using System.Data; using System.Reflection; using System.Text;
namespace Apewer.Source {
/// <summary>ORM 帮助程序。</summary>
public static class SourceUtility {
#region ORM
/// <summary>读取所有行,生成列表。</summary>
public static T[] Fill<T>(this IQuery query) where T : class, new() { var objects = Fill(query, typeof(T)); var array = CollectionUtility.As<object, T>(objects); return array; }
/// <summary>读取所有行填充到 T,组成 T[]。</summary>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
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); }
/// <summary>将 Query 的行,填充到模型实体。</summary>
/// <remarks>填充失败时返回 NULL 值。</remarks>
/// <exception cref="Exception"></exception>
public static object FillRow(IQuery query, int rowIndex, Type model, TableStructure structure) => FillRow(query?.Table, rowIndex, model, structure);
/// <summary>将 Query 的行,填充到模型实体。</summary>
/// <remarks>填充失败时返回 NULL 值。</remarks>
/// <exception cref="Exception"></exception>
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<DateTime>))) setter.Invoke(record, new object[] { new Nullable<DateTime>((DateTime)value) }); else if (pt.Equals(typeof(Nullable<bool>))) setter.Invoke(record, new object[] { new Nullable<bool>(NumberUtility.Boolean(value)) }); else if (pt.Equals(typeof(Nullable<byte>))) setter.Invoke(record, new object[] { new Nullable<byte>(NumberUtility.Byte(value)) }); else if (pt.Equals(typeof(Nullable<sbyte>))) setter.Invoke(record, new object[] { new Nullable<sbyte>(NumberUtility.SByte(value)) }); else if (pt.Equals(typeof(Nullable<short>))) setter.Invoke(record, new object[] { new Nullable<short>(NumberUtility.Int16(value)) }); else if (pt.Equals(typeof(Nullable<ushort>))) setter.Invoke(record, new object[] { new Nullable<int>(NumberUtility.UInt16(value)) }); else if (pt.Equals(typeof(Nullable<int>))) setter.Invoke(record, new object[] { new Nullable<int>(NumberUtility.Int32(value)) }); else if (pt.Equals(typeof(Nullable<uint>))) setter.Invoke(record, new object[] { new Nullable<uint>(NumberUtility.UInt32(value)) }); else if (pt.Equals(typeof(Nullable<long>))) setter.Invoke(record, new object[] { new Nullable<long>(NumberUtility.Int64(value)) }); else if (pt.Equals(typeof(Nullable<ulong>))) setter.Invoke(record, new object[] { new Nullable<ulong>(NumberUtility.UInt64(value)) }); else if (pt.Equals(typeof(Nullable<float>))) setter.Invoke(record, new object[] { new Nullable<float>(NumberUtility.Single(value)) }); else if (pt.Equals(typeof(Nullable<double>))) setter.Invoke(record, new object[] { new Nullable<double>(NumberUtility.Double(value)) }); else if (pt.Equals(typeof(Nullable<decimal>))) setter.Invoke(record, new object[] { new Nullable<decimal>(NumberUtility.Decimal(value)) }); #endif
else { try { setter.Invoke(record, new object[] { value }); return true; } catch { } } return false; }
/// <summary>解析 DataTable,填充没行到到指定的类型中,形成数组。</summary>
/// <param name="table">将要读取的表。</param>
/// <param name="compatible">当类型不同时,尝试转换以兼容。</param>
/// <returns>由指定类型组成的数组。</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public static T[] Fill<T>(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; }
/// <summary>解析 DataTable,填充没行到到指定的类型中,形成数组。</summary>
/// <param name="table">将要读取的表。</param>
/// <param name="model">要填充的目标类型,必须是可实例化的引用类型。</param>
/// <param name="compatible">当类型不同时,尝试转换以兼容。</param>
/// <returns>由指定类型组成的数组。</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
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<DateTime>))) prop.SetValue(record, new Nullable<DateTime>((DateTime)value), null); else if (pt.Equals(typeof(Nullable<bool>))) prop.SetValue(record, new Nullable<bool>(NumberUtility.Boolean(value)), null); else if (pt.Equals(typeof(Nullable<byte>))) prop.SetValue(record, new Nullable<byte>(NumberUtility.Byte(value)), null); else if (pt.Equals(typeof(Nullable<sbyte>))) prop.SetValue(record, new Nullable<sbyte>(NumberUtility.SByte(value)), null); else if (pt.Equals(typeof(Nullable<short>))) prop.SetValue(record, new Nullable<short>(NumberUtility.Int16(value)), null); else if (pt.Equals(typeof(Nullable<ushort>))) prop.SetValue(record, new Nullable<int>(NumberUtility.UInt16(value)), null); else if (pt.Equals(typeof(Nullable<int>))) prop.SetValue(record, new Nullable<int>(NumberUtility.Int32(value)), null); else if (pt.Equals(typeof(Nullable<uint>))) prop.SetValue(record, new Nullable<uint>(NumberUtility.UInt32(value)), null); else if (pt.Equals(typeof(Nullable<long>))) prop.SetValue(record, new Nullable<long>(NumberUtility.Int64(value)), null); else if (pt.Equals(typeof(Nullable<ulong>))) prop.SetValue(record, new Nullable<ulong>(NumberUtility.UInt64(value)), null); else if (pt.Equals(typeof(Nullable<float>))) prop.SetValue(record, new Nullable<float>(NumberUtility.Single(value)), null); else if (pt.Equals(typeof(Nullable<double>))) prop.SetValue(record, new Nullable<double>(NumberUtility.Double(value)), null); else if (pt.Equals(typeof(Nullable<decimal>))) prop.SetValue(record, new Nullable<decimal>(NumberUtility.Decimal(value)), null);
else { try { prop.SetValue(record, value, null); return true; } catch { } } return false; }
#endregion
#region Record
/// <summary>修复记录属性。</summary>
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; } }
/// <summary>设置 Updated 属性。</summary>
/// <returns>TRUE:设置成功;FALSE:设置失败。</returns>
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; }
/// <summary>枚举带有 Table 特性的 <typeparamref name="T"/> 派生类型。</summary>
public static Type[] EnumerateRecords<T>() where T : IRecord => EnumerateRecords(typeof(T));
/// <summary>枚举带有 Table 特性的派生类型。</summary>
/// <exception cref="ArgumentNullException"></exception>
public static Type[] EnumerateRecords(Type baseType) { if (baseType == null) throw new ArgumentNullException(nameof(baseType)); var assemblies = AppDomain.CurrentDomain.GetAssemblies(); var builder = new ArrayBuilder<Type>(); 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<TableAttribute>(type, false)) return false; if (type.Equals(@base)) return true; if (RuntimeUtility.IsInherits(type, @base)) return true; return false; }
#endregion
#region Query
/// <summary>简单查询:取结果中第 0 列所有单元格的文本形式,可指定查询后关闭服务器连接,返回结果中不包含无效文本。</summary>
/// <param name="source">数据库客户端。</param>
/// <param name="sql">用于查询的 SQL 语句。</param>
/// <param name="parameters">SQL 参数。</param>
/// <exception cref="SqlException"></exception>
public static string[] Column(this IDbAdo source, string sql, object parameters = null) { if (source == null) return new string[0];
var pool = null as string[]; var rows = 0; var count = 0; using (var query = source.Query(sql, parameters)) { 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; }
/// <summary>简单查询:取结果中第 0 行、第 0 列单元格中的文本,可指定查询后关闭服务器连接。</summary>
/// <param name="dbClient">数据库客户端。</param>
/// <param name="sql">用于查询的 SQL 语句。</param>
/// <param name="parameters">SQL 参数。</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="SqlException"></exception>
public static string Cell(this IDbAdo dbClient, string sql, object parameters = null) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));
using (var query = dbClient.Query(sql, parameters)) { if (!query.Success) throw new SqlException(query, sql); var value = TextUtility.Trim(query.Text(0, 0)); return value; } }
/// <summary>查询。</summary>
/// <param name="dbClient">数据库连接。</param>
/// <param name="sql">SQL 语句。</param>
/// <param name="parameters">SQL 参数。</param>
/// <exception cref="ArgumentNullException"></exception>
public static IQuery Query(this IDbAdo dbClient, string sql, IEnumerable<KeyValuePair<string, object>> 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); }
/// <summary>查询。</summary>
/// <param name="dbClient">数据库连接。</param>
/// <param name="sql">SQL 语句。</param>
/// <param name="parameters">参数容器,每个属性表示一个 SQL 参数。此方法将会自动补足参数名称的 @ 前缀。</param>
/// <exception cref="ArgumentNullException"></exception>
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<KeyValuePair<string, object>> kvps) { var ps = Parameters(dbClient, sql, kvps); return dbClient.Query(sql, ps); } else { var ps = ParametersByProperites(dbClient, sql, parameters); return dbClient.Query(sql, ps); } }
/// <summary>执行 SELECT 语句,获取查询结果。</summary>
/// <param name="connection">数据库连接。</param>
/// <param name="transaction">事务。</param>
/// <param name="sql">SQL 语句。</param>
/// <param name="parameters">参数。</param>
/// <param name="timeout">超时秒数。</param>
/// <returns>查询结果。</returns>
public static DataTable Query(this IDbConnection connection, IDbTransaction transaction, string sql, IEnumerable<IDbDataParameter> 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; } } }
/// <summary>查询。</summary>
/// <param name="dbClient">数据库连接。</param>
/// <param name="sql">SQL 语句。</param>
/// <param name="parameters">参数容器,每个属性表示一个 SQL 参数。此方法将会自动补足参数名称的 @ 前缀。</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="NotImplementedException"></exception>
public static T[] Query<T>(this IDbOrm dbClient, string sql, object parameters = null) where T : class, new() { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));
if (dbClient is IDbAdo ado) { if (parameters is IEnumerable<KeyValuePair<string, object>> kvps) { var ps = Parameters(ado, sql, kvps); return dbClient.Query<T>(sql, ps); } else { var ps = ParametersByProperites(ado, sql, parameters); return dbClient.Query<T>(sql, ps); } } else { throw new NotImplementedException($"连接未实现 {nameof(IDbAdo)} 接口,无法创建参数。"); } }
#endregion
#region Execute
/// <summary>执行 SQL 语句,并加入参数。</summary>
/// <exception cref="ArgumentNullException"></exception>
public static IExecute Execute(this IDbAdo dbClient, string sql, IEnumerable<KeyValuePair<string, object>> 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); }
/// <summary>执行 SQL 语句,并加入参数。</summary>
/// <param name="dbClient">数据库连接。</param>
/// <param name="sql">SQL 语句。</param>
/// <param name="parameters">参数容器,每个属性表示一个 SQL 参数。此方法将会自动补足参数名称的 @ 前缀。</param>
/// <param name="autoTransaction">自动使用事务。</param>
/// <exception cref="ArgumentNullException"></exception>
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<KeyValuePair<string, object>> 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); } }
/// <summary>执行 SQL 语句,获取影响的行数。</summary>
/// <param name="connection">数据库连接。</param>
/// <param name="transaction">事务。</param>
/// <param name="sql">SQL 语句。</param>
/// <param name="parameters">参数。</param>
/// <param name="timeout">超时秒数。</param>
/// <returns>行数。</returns>
public static int Execute(this IDbConnection connection, IDbTransaction transaction, string sql, IEnumerable<IDbDataParameter> 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
/// <summary>启动事务,执行指定的过程并在完成后提交事务。若过程被异常打断,则回滚事务。</summary>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="SqlException"></exception>
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<object>(source, () => { action.Invoke(); return null; }); }
/// <summary>启动事务,执行指定的过程并在完成后提交事务。若过程被异常打断,则回滚事务。</summary>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="SqlException"></exception>
public static T InTransaction<T>(this IDbAdo source, Func<T> 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
/// <exception cref="ArgumentNullException"></exception>
static List<IDataParameter> 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<string, object>(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<IDataParameter>(); foreach (var kvp in dict) { var p = dbClient.Parameter(kvp.Key, kvp.Value); ps.Add(p); } return ps; }
/// <exception cref="ArgumentNullException"></exception>
static List<IDataParameter> Parameters(IDbAdo dbClient, string sql, IEnumerable<KeyValuePair<string, object>> parameters) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); if (parameters == null) return null;
var lsql = sql.Lower(); var names = new List<string>(20); var ps = new List<IDataParameter>(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
/// <summary>对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。</summary>
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; }
/// <summary>限定名称文本,只允许包含字母、数字和下划线。</summary>
public static string SafeName(this string name) => TextUtility.Restrict(name, "0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
#endregion
#region 数据模型 -> DataTable
/// <summary>将多个实体元素转换为 DataTable。</summary>
/// <typeparam name="T">实体元素的类型。</typeparam>
/// <param name="items">实体元素。</param>
/// <param name="tableName">设置 <see cref="DataTable"/> 的名称。</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="DuplicateNameException"></exception>
/// <exception cref="InvalidExpressionException"></exception>
public static DataTable DataTable<T>(this IEnumerable<T> 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<object>(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 序列化
/// <summary>转换 <see cref="System.Data.DataTable"/> 到 <see cref="ObjectSet{T}"/> 数组,每行记录为一个 ObjectSet 对象。</summary>
/// <returns>当参数 table 无效时返回 0 长度的 <see cref="ObjectSet{T}"/> 数组。</returns>
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<string, object>[rows]; for (var r = 0; r < table.Rows.Count; r++) { var dict = new Dictionary<string, object>(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; }
/// <summary>转换为 Json 对象。</summary>
public static Json ToJson(this DataTable table, Func<DateTime, string> 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; }
/// <summary>转换为 Json 对象。</summary>
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; }
/// <summary>转换为 Json 对象。</summary>
public static Json ToJson(this DataRowCollection rows, Func<DateTime, object> 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; }
/// <summary>转换为 Json 对象。</summary>
public static Json ToJson(this DataRow row, Func<DateTime, object> 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; }
/// <summary>转换 <see cref="DataTable"/> 为 CSV 文本,不存在表时返回 NULL 值。可指定是否包含表头。</summary>
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 快捷操作
/// <summary>获取默认表中指定单元格的内容。</summary>
/// <param name="table">数据表。</param>
/// <param name="rowIndex">行索引,从 0 开始。</param>
/// <param name="columnIndex">列索引,从 0 开始。</param>
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; }
/// <summary>获取默认表中指定单元的内容。</summary>
/// <param name="table">数据表。</param>
/// <param name="rowIndex">行索引,从 0 开始。</param>
/// <param name="columnName">列名称/字段名称,此名称不区分大小写。</param>
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; }
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
public static Class<DateTime> DateTime(this DataTable table, int row, int column) => table == null ? null : ClockUtility.DateTime(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
public static Class<DateTime> DateTime(this DataTable table, int row, string column) => table == null ? null : ClockUtility.DateTime(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
public static Int32 Int32(this DataTable table, int row, int column) => table == null ? 0 : NumberUtility.Int32(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
public static Int32 Int32(this DataTable table, int row, string column) => table == null ? 0 : NumberUtility.Int32(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
public static Int64 Int64(this DataTable table, int row, int column) => table == null ? 0L : NumberUtility.Int64(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
public static Int64 Int64(this DataTable table, int row, string column) => table == null ? 0L : NumberUtility.Int64(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
public static Decimal Decimal(this DataTable table, int row, int column) => table == null ? 0M : NumberUtility.Decimal(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
public static Decimal Decimal(this DataTable table, int row, string column) => table == null ? 0M : NumberUtility.Decimal(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>>
public static Double Double(this DataTable table, int row, int column) => table == null ? 0D : NumberUtility.Double(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>>
public static Double Double(this DataTable table, int row, string column) => table == null ? 0D : NumberUtility.Double(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
public static string Text(this DataTable table, int row, int column) => table == null ? null : TextUtility.Text(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
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
/// <summary>转换 ObjectSet 数组为 dynamic 数组。</summary>
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 表达式计算
/// <summary>计算文本表达式。</summary>
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
}
}
|