From a0c9eb41d77072578520d874d03a5d208f90df30 Mon Sep 17 00:00:00 2001 From: Elivo Date: Tue, 20 Sep 2022 13:49:16 +0800 Subject: [PATCH] Apewer-6.7.0 --- Apewer.Source/Source/Access.cs | 20 +- Apewer.Source/Source/DbClient.cs | 205 +++---- Apewer.Source/Source/MySql.cs | 50 +- Apewer.Source/Source/SqlClient.cs | 87 +-- Apewer.Source/Source/SqlServerSouce.cs | 26 - Apewer.Source/Source/Sqlite.cs | 70 ++- Apewer/Apewer.csproj | 1 + Apewer/Apewer.props | 14 +- Apewer/ClockUtility.cs | 42 +- ...llectionHelper.cs => CollectionUtility.cs} | 502 ++++++++++++------ .../Utilities/LinqBridge.cs | 23 - Apewer/Externals/System/Action.cs | 36 ++ Apewer/Externals/System/EventHandler.cs | 18 + Apewer/Externals/System/Func.cs | 33 ++ Apewer/Externals/System/Linq/Enumerable.cs | 42 ++ Apewer/Json.cs | 2 +- Apewer/Network/Extension.cs | 121 +++++ Apewer/Network/HttpClient.cs | 6 +- Apewer/Network/Icmp.cs | 63 --- Apewer/NetworkUtility.cs | 31 ++ Apewer/Result.cs | 128 ----- Apewer/RuntimeUtility.cs | 27 +- Apewer/Source/ColumnAttribute.cs | 14 +- Apewer/Source/IDbOrm.cs | 39 +- Apewer/Source/IRecordPrimaryKey.cs | 11 + Apewer/Source/IndexAttribute.cs | 89 ---- Apewer/Source/KeyRecord.cs | 26 + Apewer/Source/ModelException.cs | 99 ++++ Apewer/Source/SourceUtility.cs | 426 +++++++-------- Apewer/Source/SqlException.cs | 71 +++ Apewer/Source/TableAttribute.cs | 6 + Apewer/Source/TableStructure.cs | 81 ++- Apewer/StorageUtility.cs | 2 + Apewer/TextUtility.cs | 41 +- Apewer/Web/ApiModel.cs | 10 +- Apewer/Web/ApiProcessor.cs | 4 + Apewer/Web/ApiResponse.cs | 2 + Apewer/Web/ApiUtility.cs | 7 + Apewer/_Delegates.cs | 71 --- Apewer/_Extensions.cs | 51 +- ChangeLog.md | 15 + 41 files changed, 1520 insertions(+), 1092 deletions(-) delete mode 100644 Apewer.Source/Source/SqlServerSouce.cs rename Apewer/{Internals/CollectionHelper.cs => CollectionUtility.cs} (59%) create mode 100644 Apewer/Externals/System/Action.cs create mode 100644 Apewer/Externals/System/EventHandler.cs create mode 100644 Apewer/Externals/System/Func.cs create mode 100644 Apewer/Externals/System/Linq/Enumerable.cs delete mode 100644 Apewer/Network/Icmp.cs delete mode 100644 Apewer/Result.cs create mode 100644 Apewer/Source/IRecordPrimaryKey.cs delete mode 100644 Apewer/Source/IndexAttribute.cs create mode 100644 Apewer/Source/KeyRecord.cs create mode 100644 Apewer/Source/ModelException.cs create mode 100644 Apewer/Source/SqlException.cs diff --git a/Apewer.Source/Source/Access.cs b/Apewer.Source/Source/Access.cs index 40fbfc8..94ebac1 100644 --- a/Apewer.Source/Source/Access.cs +++ b/Apewer.Source/Source/Access.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Data; using System.Data.OleDb; -using System.Drawing; using System.IO; using System.Text; using static Apewer.Source.SourceUtility; @@ -14,7 +13,7 @@ using static Apewer.Source.SourceUtility; namespace Apewer.Source { - /// 用于快速连接 Microsoft Access 数据库的辅助。 + /// 连接 Access 数据库的客户端。 public abstract partial class Access { @@ -80,7 +79,7 @@ namespace Apewer.Source public override string[] StoreNames() => throw new InvalidOperationException(); /// - public override string[] TableNames() => TextColumn("select name from msysobjects where type=1 and flags = 0"); + public override string[] TableNames() => QueryStrings("select name from msysobjects where type=1 and flags = 0"); /// public override string Insert(object record, string table = null, bool adjust = true) @@ -271,21 +270,21 @@ namespace Apewer.Source } /// - protected override string KeysSql(string tableName, string keyField, string flagField, long flagValue) + protected override string Keys(string tableName, string keyField, string flagField, long flagValue) { if (flagValue == 0) return $"select [{keyField}] from [{tableName}]"; else return $"select [{keyField}] from [{tableName}] where [{flagField}] = {flagValue}"; } /// - protected override string RecordSql(string tableName, string keyField, string keyValue, string flagField, long flagValue) + protected override string Get(string tableName, string keyField, string keyValue, string flagField, long flagValue) { if (flagValue == 0) return $"select top 1 * from [{tableName}] where [{keyField}] = '{keyValue}'"; else return $"select top 1 * from [{tableName}] where [{keyField}] = '{keyValue}' and [{flagField}] = {flagValue}"; } /// - protected override string RecordsSql(string tableName, string flagField, long flagValue) + protected override string List(string tableName, string flagField, long flagValue) { if (flagValue == 0) return $"select * from [{tableName}]"; else return $"select * from [{tableName}] where [{flagField}] = {flagValue}"; @@ -406,9 +405,10 @@ namespace Apewer.Source #region protected /// 获取或设置连接字符串。 + /// internal protected static string GenerateCS(string provider, string path, string pass, string jo) { - if (!File.Exists(path)) return null; + if (!File.Exists(path)) throw new FileNotFoundException("文件不存在。", path); var sb = new StringBuilder(); @@ -486,12 +486,13 @@ namespace Apewer.Source } /// 使用 Microsoft.Jet.OLEDB.4.0 访问 Access 97 - 2003 数据库文件。 - public sealed class AccessJet4 : Access + public class AccessJet4 : Access { const string JetOleDB4 = "microsoft.jet.oledb.4.0"; /// 创建 Access 类的新实例。 + /// public AccessJet4(string path, string pass = null, string jo = null, Timeout timeout = null) : base(GenerateCS(JetOleDB4, path, pass, jo), timeout) { } @@ -503,12 +504,13 @@ namespace Apewer.Source } /// 使用 Microsoft.ACE.OLEDB.12.0 访问 Access 2007 数据库文件。 - public sealed class AccessAce12 : Access + public class AccessAce12 : Access { const string AceOleDB12 = "microsoft.ace.oledb.12.0"; /// 创建 Access 类的新实例。 + /// public AccessAce12(string path, string pass = null, string jo = null, Timeout timeout = null) : base(GenerateCS(AceOleDB12, path, pass, jo), timeout) { } diff --git a/Apewer.Source/Source/DbClient.cs b/Apewer.Source/Source/DbClient.cs index 0ec9066..b1a90e9 100644 --- a/Apewer.Source/Source/DbClient.cs +++ b/Apewer.Source/Source/DbClient.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.Common; -using System.Reflection; -using System.Text; namespace Apewer.Source { @@ -21,7 +18,7 @@ namespace Apewer.Source _timeout = timeout ?? Timeout.Default; } - #region connection + #region Connection Timeout _timeout = null; IDbConnection _conn = null; @@ -84,7 +81,7 @@ namespace Apewer.Source } /// 关闭连接,并释放对象所占用的系统资源。 - public void Close() + public virtual void Close() { if (_conn != null) { @@ -100,7 +97,7 @@ namespace Apewer.Source } /// 关闭连接,释放对象所占用的系统资源,并清除连接信息。 - public void Dispose() + public virtual void Dispose() { Close(); } @@ -110,7 +107,7 @@ namespace Apewer.Source #endregion - #region transaction + #region Transaction private IDbTransaction _transaction = null; private bool _autocommit = false; @@ -205,9 +202,11 @@ namespace Apewer.Source #endregion - #region ado + #region ADO /// 查询。 + /// SQL 语句。 + /// 为 SQL 语句提供的参数。 public IQuery Query(string sql, IEnumerable parameters = null) { if (TextUtility.IsEmpty(sql)) return new Query(false, "语句无效。"); @@ -268,7 +267,32 @@ namespace Apewer.Source } } - /// 执行 SQL 语句,并加入参数。 + /// 输出查询结果的首列数据。 + /// + protected string[] QueryStrings(string sql, string[] excluded = null) + { + if (Connect().NotEmpty()) return new string[0]; + using (var query = Query(sql)) + { + if (!query.Success) throw new SqlException(query, sql); + + var rows = query.Rows; + var list = new List(rows); + for (int r = 0; r < query.Rows; r++) + { + var cell = query.Text(r, 0); + if (TextUtility.IsEmpty(cell)) continue; + if (excluded != null && excluded.Contains(cell)) continue; + list.Add(cell); + } + return list.ToArray(); + } + } + + /// 执行。 + /// SQL 语句。 + /// 为 SQL 语句提供的参数。 + /// 自动启动事务。 public IExecute Execute(string sql, IEnumerable parameters = null, bool autoTransaction = false) { if (TextUtility.IsEmpty(sql)) return new Execute(false, "语句无效。"); @@ -310,28 +334,18 @@ namespace Apewer.Source } } - /// 输出查询结果的首列数据。 - protected string[] TextColumn(string sql, string[] excluded = null) - { - if (Connect().NotEmpty()) return new string[0]; - using (var query = Query(sql)) - { - var rows = query.Rows; - var list = new List(rows); - for (int r = 0; r < query.Rows; r++) - { - var cell = query.Text(r, 0); - if (TextUtility.IsEmpty(cell)) continue; - if (excluded != null && excluded.Contains(cell)) continue; - list.Add(cell); - } - return list.ToArray(); - } - } + /// 查询数据库中的所有表名。 + public abstract string[] TableNames(); + + /// 查询数据库实例中的所有数据库名。 + public abstract string[] StoreNames(); + + /// 查询表中的所有列名。 + public abstract string[] ColumnNames(string tableName); #endregion - #region parameter + #region Parameter /// 创建参数。 /// @@ -360,7 +374,22 @@ namespace Apewer.Source #endregion - #region orm + #region ORM + + /// 检查数据模型结构,存在异常时抛出异常。 + /// + /// + protected static TableStructure Parse(Type model) + { + if (model == null) throw new ArgumentNullException(nameof(model), "数据模型类型无效。"); + + var ts = TableStructure.Parse(model); + if (ts == null) throw ModelException.InvalidStructure(model); + if (ts.TableName.IsEmpty()) throw ModelException.InvalidTableName(ts.Model); + if (ts.Key == null || ts.Key.Field.IsEmpty()) throw ModelException.MissingKey(ts.Model); + if (ts.Flag == null || ts.Flag.Field.IsEmpty()) throw ModelException.MissingFlag(ts.Model); + return ts; + } /// 初始化指定类型,以创建表或增加字段。 /// 指定新的表名。 @@ -402,35 +431,32 @@ namespace Apewer.Source /// 目标记录的类型。 /// 要执行的 SQL 语句。 /// 为 SQL 语句提供的参数。 - public Result Query(Type model, string sql, IEnumerable parameters = null) + /// + /// + /// + /// + public object[] Query(Type model, string sql, IEnumerable parameters = null) { - if (model == null) return new Result("数据模型类型无效。"); - if (string.IsNullOrEmpty(sql)) return new Result("SQL 语句无效。"); - + if (string.IsNullOrEmpty(sql)) throw new ArgumentNullException(nameof(sql), "SQL 语句无效。"); using (var query = Query(sql, parameters)) { - var result = null as Result; - if (query.Success) - { - try - { - var array = SourceUtility.Fill(query, model); - return new Result(array); - } - catch (Exception ex) { return new Result(ex); } - } - else return new Result(query.Message); + if (!query.Success) throw new SqlException(query, sql); + return SourceUtility.Fill(query, model); } } /// 使用指定语句查询,获取查询结果。 /// 要执行的 SQL 语句。 /// 为 SQL 语句提供的参数。 - public Result Query(string sql, IEnumerable parameters = null) where T : class, new() => Query(typeof(T), sql, parameters).As(); + /// + /// + /// + /// + public T[] Query(string sql, IEnumerable parameters = null) where T : class, new() => Query(typeof(T), sql, parameters).As(); #endregion - #region record + #region ORM: Record /// 更新记录。 /// 要更新的记录实体。 @@ -439,83 +465,89 @@ namespace Apewer.Source /// 错误信息。当成功时候返回空字符串。 public abstract string Update(IRecord record, string table = null, bool adjust = true); - /// - protected abstract string KeysSql(string tableName, string keyField, string flagField, long flagValue); + /// 生成用于 Keys 方法的 SQL 语句。 + protected abstract string Keys(string tableName, string keyField, string flagField, long flagValue); /// 获取指定类型的主键,按 Flag 属性筛选。 /// 要查询的类型。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 /// - public Result Keys(Type model, long flag = 0) + /// + /// + public string[] Keys(Type model, long flag = 0) { - if (model == null) throw new ArgumentNullException(nameof(model)); - - var ts = TableStructure.Parse(model); - if (ts.TableName.IsEmpty()) return new Result($"类型 <{model.FullName}> 中不包含表名称。"); - if (ts.Key == null || ts.Key.Field.IsEmpty()) return new Result($"类型 <{model.FullName}> 中不包含 Key 的字段。"); - if (ts.Flag == null || ts.Flag.Field.IsEmpty()) return new Result($"类型 <{model.FullName}> 中不包含 Flag 的字段。"); - - var sql = KeysSql(ts.TableName, ts.Key.Field, ts.Flag.Field, flag); - return new Result(TextColumn(sql)); + var ts = Parse(model); + var sql = Keys(ts.TableName, ts.Key.Field, ts.Flag.Field, flag); + return QueryStrings(sql); } /// 获取指定类型的主键,按 Flag 属性筛选。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Keys(long flag = 0) where T : class, IRecord, new() => Keys(typeof(T), flag); + /// + /// + public string[] Keys(long flag = 0) where T : class, IRecord, new() => Keys(typeof(T), flag); - /// - protected abstract string RecordSql(string tableName, string keyField, string keyValue, string flagField, long flagValue); + /// 生成用于 Get 方法的 SQL 语句。 + protected abstract string Get(string tableName, string keyField, string keyValue, string flagField, long flagValue); /// 获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。 /// 目标记录的类型。 /// 目标记录的主键。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Record(Type model, string key, long flag = 0) + /// + /// + /// + public object Get(Type model, string key, long flag = 0) { if (model == null) throw new ArgumentNullException(nameof(model)); var ts = TableStructure.Parse(model); - if (ts.TableName.IsEmpty()) return new Result($"类型 <{model.FullName}> 中不包含表名称。"); - if (ts.Key == null || ts.Key.Field.IsEmpty()) return new Result($"类型 <{model.FullName}> 中不包含 Key 的字段。"); - if (ts.Flag == null || ts.Flag.Field.IsEmpty()) return new Result($"类型 <{model.FullName}> 中不包含 Flag 的字段。"); + if (ts == null) throw new ModelException($"无法解析类型 {model.Name}。", model); + + key = key.SafeKey(); + if (key.IsEmpty()) return null; - var sql = RecordSql(ts.TableName, ts.Key.Field, key, ts.Flag.Field, flag); + var sql = Get(ts.TableName, ts.Key.Field, key, ts.Flag.Field, flag); var records = Query(model, sql, null); - if (records) return new Result(records.Value.First()); - else return new Result(records.Message); + return records.First(); } /// 获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。 /// 目标记录的主键。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Record(string key, long flag = 0) where T : class, IRecord, new() => Record(typeof(T), key, flag).As(); + /// + /// + public T Get(string key, long flag = 0) where T : class, IRecord, new() => Get(typeof(T), key, flag) as T; - /// - protected abstract string RecordsSql(string tableName, string flagField, long flagValue); + /// 生成用于 List 方法的 SQL 语句。 + protected abstract string List(string tableName, string flagField, long flagValue); /// 查询所有记录,可按 Flag 筛选。 /// 目标记录的类型。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Records(Type model, long flag = 0) + /// + /// + /// + public object[] List(Type model, long flag = 0) { if (model == null) throw new ArgumentNullException(nameof(model)); - var ts = TableStructure.Parse(model); - if (ts.TableName.IsEmpty()) return new Result($"类型 <{model.FullName}> 中不包含表名称。"); - if (ts.Key == null || ts.Key.Field.IsEmpty()) return new Result($"类型 <{model.FullName}> 中不包含 Key 的字段。"); - if (ts.Flag == null || ts.Flag.Field.IsEmpty()) return new Result($"类型 <{model.FullName}> 中不包含 Flag 的字段。"); + var ts = Parse(model); + if (ts == null) throw new ModelException($"无法解析类型 {model.Name}。", model); - var sql = RecordsSql(ts.TableName, ts.Flag.Field, flag); + var sql = List(ts.TableName, ts.Flag.Field, flag); return Query(model, sql, null); } /// 查询所有记录,可按 Flag 筛选。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Records(long flag = 0) where T : class, IRecord, new() => Records(typeof(T), flag).As(); + /// + /// + public T[] List(long flag = 0) where T : class, IRecord, new() => List(typeof(T), flag).As(); #endregion - #region static + #region Static /// 获取表名。 protected static string Table() => Table(typeof(T)); @@ -533,7 +565,7 @@ namespace Apewer.Source #endregion - #region derived + #region Derived /// 为 Ado 创建 IDataAdapter 对象。 protected abstract IDataAdapter CreateDataAdapter(IDbCommand command); @@ -546,19 +578,6 @@ namespace Apewer.Source #endregion - #region initialization - - /// 查询数据库中的所有表名。 - public abstract string[] TableNames(); - - /// 查询数据库实例中的所有数据库名。 - public abstract string[] StoreNames(); - - /// 查询表中的所有列名。 - public abstract string[] ColumnNames(string tableName); - - #endregion - } } diff --git a/Apewer.Source/Source/MySql.cs b/Apewer.Source/Source/MySql.cs index 573b8fd..39cc02d 100644 --- a/Apewer.Source/Source/MySql.cs +++ b/Apewer.Source/Source/MySql.cs @@ -1,24 +1,18 @@ #if MYSQL_6_9 || MYSQL_6_10 -/* 2021.11.07 */ - using Externals.MySql.Data.MySqlClient; using System; using System.Collections.Generic; using System.Data; -using System.Drawing; -using System.Net; -using System.Security.Cryptography.X509Certificates; using System.Text; -using System.Transactions; using static Apewer.Source.SourceUtility; namespace Apewer.Source { - /// - public sealed class MySql : DbClient + /// 连接 MySQL 数据库的客户端。 + public class MySql : DbClient { #region connection @@ -47,13 +41,13 @@ namespace Apewer.Source } /// 构建连接字符串以创建实例。 + /// public MySql(string address, string store, string user, string pass, Timeout timeout = null) : base(timeout) { - var a = address ?? ""; - var s = store ?? ""; - var u = user ?? ""; - var p = pass ?? ""; - var cs = $"server={a}; database={s}; uid={u}; pwd={p}; "; + if (string.IsNullOrEmpty(address)) throw new ArgumentNullException(nameof(address)); + if (string.IsNullOrEmpty(store)) store = "mysql"; + if (string.IsNullOrEmpty(user)) user = "root"; + var cs = $"server={address}; database={store}; uid={user}; pwd={pass ?? ""}; "; _connstr = cs; } @@ -96,7 +90,7 @@ namespace Apewer.Source { var store = StoreName(); var sql = $"select table_name from information_schema.tables where table_schema='{store}' and table_type='base table'"; - return TextColumn(sql); + return QueryStrings(sql); } /// @@ -105,7 +99,7 @@ namespace Apewer.Source var store = StoreName(); var table = TextUtility.AntiInject(tableName); var sql = $"select column_name from information_schema.columns where table_schema='{store}' and table_name='{table}'"; - return TextColumn(sql); + return QueryStrings(sql); } /// @@ -302,21 +296,21 @@ namespace Apewer.Source } /// - protected override string KeysSql(string tableName, string keyField, string flagField, long flagValue) + protected override string Keys(string tableName, string keyField, string flagField, long flagValue) { if (flagValue == 0) return $"select `{keyField}` from `{tableName}`"; else return $"select `{keyField}` from `{tableName}` where `{flagField}` = {flagValue}"; } /// - protected override string RecordSql(string tableName, string keyField, string keyValue, string flagField, long flagValue) + protected override string Get(string tableName, string keyField, string keyValue, string flagField, long flagValue) { if (flagValue == 0) return $"select * from `{tableName}` where `{keyField}` = '{keyValue}' limit 1"; else return $"select * from `{tableName}` where `{keyField}` = '{keyValue}' and `{flagField}` = {flagValue} limit 1"; } /// - protected override string RecordsSql(string tableName, string flagField, long flagValue) + protected override string List(string tableName, string flagField, long flagValue) { if (flagValue == 0) return $"select * from `{tableName}`"; else return $"select * from `{tableName}` where `{flagField}` = {flagValue}"; @@ -333,27 +327,31 @@ namespace Apewer.Source { var store = StoreName(); var sql = $"select table_name from information_schema.tables where table_schema='{store}' and table_type='view'"; - return TextColumn(sql); + return QueryStrings(sql); } /// 获取记录。 /// 填充的记录模型。 /// 要跳过的记录数,可用最小值为 0。 /// 要获取的记录数,可用最小值为 1。 - public Result Range(Type model, int skip, int count) where T : class, new() + /// + /// + /// + public T[] Range(Type model, int skip, int count) where T : class, new() { - if (model == null) return new Result("参数 model 无效。"); - if (skip < 0) return new Result("参数 skip 超出了范围。"); - if (count < 1) return new Result("参数 count 超出了范围。"); + if (model == null) throw new ArgumentNullException(nameof(model)); + if (skip < 0) throw new ArgumentOutOfRangeException(nameof(skip)); + if (count < 1) throw new ArgumentOutOfRangeException(nameof(count)); var ts = TableStructure.Parse(model); - if (ts.TableName.IsEmpty()) return new Result($"无法解析类型 {model.FullName}。"); + if (ts.TableName.IsEmpty()) throw ModelException.InvalidTableName(model); var sql = $"select * from `{ts.TableName}` limit {skip}, {count}"; using (var query = Query(sql)) { - if (!query.Success) return new Result(query.Message); - return new Result(query.Fill()); + if (!query.Success) throw new SqlException(query, sql); + var array = query.Fill(); + return array; } } diff --git a/Apewer.Source/Source/SqlClient.cs b/Apewer.Source/Source/SqlClient.cs index 51670a2..db2261a 100644 --- a/Apewer.Source/Source/SqlClient.cs +++ b/Apewer.Source/Source/SqlClient.cs @@ -1,15 +1,10 @@ -/* 2021.12.07 */ - -using Apewer; -using System; +using System; using System.Collections.Generic; using System.Data; -using System.Data.Common; +using System.Data.SqlClient; using System.Text; using static Apewer.Source.SourceUtility; -using System.Data.SqlClient; -using System.IO; #if NETFRAMEWORK using System.Data.Sql; @@ -18,9 +13,9 @@ using System.Data.Sql; namespace Apewer.Source { - /// + /// 连接 SQL Server 数据库的客户端。 [Serializable] - public sealed class SqlClient : DbClient + public class SqlClient : DbClient { #region connection @@ -49,18 +44,18 @@ namespace Apewer.Source } /// 使用连接凭据创建数据库连接实例。 + /// public SqlClient(string address, string store, string user, string pass, Timeout timeout = null) : base(timeout) { - var a = address ?? ""; - var s = store ?? ""; - var u = user ?? ""; - var p = pass ?? ""; - var cs = $"data source = {a}; initial catalog = {s}; "; - if (string.IsNullOrEmpty(u)) cs += "integrated security = sspi; "; + if (address.IsEmpty()) throw new ArgumentNullException(nameof(address)); + if (store.IsEmpty()) store = "master"; + + var cs = $"data source = {address ?? ""}; initial catalog = {store}; "; + if (string.IsNullOrEmpty(user)) cs += "integrated security = sspi; "; else { - cs += $"user id = {u}; "; - if (!string.IsNullOrEmpty(p)) cs += $"password = {p}; "; + cs += $"user id = {user}; "; + if (!string.IsNullOrEmpty(pass)) cs += $"password = {pass}; "; } if (timeout != null) cs += $"connection timeout = {timeout.Connect}; "; @@ -80,13 +75,13 @@ namespace Apewer.Source #region override /// 查询数据库中的所有表名。 - public override string[] TableNames() => TextColumn("select [name] from [sysobjects] where [type] = 'u' order by [name]"); + public override string[] TableNames() => QueryStrings("select [name] from [sysobjects] where [type] = 'u' order by [name]"); /// 查询数据库实例中的所有数据库名。 - public override string[] StoreNames() => TextColumn("select [name] from [master]..[sysdatabases] order by [name]", new string[] { "master", "model", "msdb", "tempdb" }); + public override string[] StoreNames() => QueryStrings("select [name] from [master]..[sysdatabases] order by [name]", new string[] { "master", "model", "msdb", "tempdb" }); /// 查询表中的所有列名。 - public override string[] ColumnNames(string tableName) => TextColumn($"select [name] from [syscolumns] where [id] = object_id('{TextUtility.AntiInject(tableName)}')"); + public override string[] ColumnNames(string tableName) => QueryStrings($"select [name] from [syscolumns] where [id] = object_id('{TextUtility.AntiInject(tableName)}')"); /// 创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。 protected override string Initialize(TableStructure structure, string table) @@ -273,21 +268,21 @@ namespace Apewer.Source protected override IDataParameter CreateParameter() => new SqlParameter(); /// - protected override string KeysSql(string tableName, string keyField, string flagField, long flagValue) + protected override string Keys(string tableName, string keyField, string flagField, long flagValue) { if (flagValue == 0) return $"select [{keyField}] from [{tableName}]"; else return $"select [{keyField}] from [{tableName}] where [{flagField}] = {flagValue}"; } /// - protected override string RecordSql(string tableName, string keyField, string keyValue, string flagField, long flagValue) + protected override string Get(string tableName, string keyField, string keyValue, string flagField, long flagValue) { if (flagValue == 0) return $"select top 1 * from [{tableName}] where [{keyField}] = '{keyValue}'"; else return $"select top 1 * from [{tableName}] where [{keyField}] = '{keyValue}' and [{flagField}] = {flagValue}"; } /// - protected override string RecordsSql(string tableName, string flagField, long flagValue) + protected override string List(string tableName, string flagField, long flagValue) { if (flagValue == 0) return $"select * from [{tableName}]"; else return $"select * from [{tableName}] where [{flagField}] = {flagValue}"; @@ -320,6 +315,7 @@ namespace Apewer.Source /// 批量插入,必须在 DataTable 中指定表名,或指定 tableName 参数。 /// /// + /// public void BulkCopy(DataTable table, string tableName = null) { // 检查 table 参数。 @@ -334,14 +330,20 @@ namespace Apewer.Source var connect = Connect(); if (connect.NotEmpty()) throw new Exception(connect); + // 准备参数。 + var options = SqlBulkCopyOptions.Default; + var trans = Transaction as SqlTransaction; + if (trans == null) options |= SqlBulkCopyOptions.UseInternalTransaction; + // 批量插入。 var bc = null as SqlBulkCopy; try { - bc = new SqlBulkCopy((SqlConnection)Connection); + bc = new SqlBulkCopy((SqlConnection)Connection, options, trans); bc.DestinationTableName = tableName; bc.BatchSize = table.Rows.Count; bc.WriteToServer(table); + try { bc.Close(); } catch { } } catch (Exception ex) { @@ -365,10 +367,10 @@ namespace Apewer.Source var connect = source.Connect(); if (connect.NotEmpty()) return "创建失败:" + connect; - var schema = source.SimpleCell("select default_schema_name from sys.database_principals where type = 'S' and name=user_name()"); + var schema = source.Cell("select default_schema_name from sys.database_principals where type = 'S' and name=user_name()"); if (schema.IsEmpty()) return "创建失败:无法获取默认模式名称。"; - var refPath = source.SimpleCell(@"select f.physical_name path from sys.filegroups g left join sys.database_files f on f.data_space_id = g.data_space_id where g.name = 'PRIMARY' and g.type = 'FG' and g.is_default = 1 and g.filegroup_guid is null"); + var refPath = source.Cell(@"select f.physical_name path from sys.filegroups g left join sys.database_files f on f.data_space_id = g.data_space_id where g.name = 'PRIMARY' and g.type = 'FG' and g.is_default = 1 and g.filegroup_guid is null"); if (refPath.IsEmpty()) return "创建失败:无法获取文件路径。"; var win = refPath.Substring(1, 2) == ":\\"; @@ -465,17 +467,20 @@ COLLATE Chinese_PRC_CI_AS #if NET20 || NET40 - /// 枚举本地网络中服务器的名称。 - public static SqlServerSource[] EnumerateServer() + /// 枚举本地网络中的 SQL Server 实例的信息。 + public static Source[] EnumerateSources() { - var list = new List(); + var list = new List(); // 表中列名:ServerName、InstanceName、IsClustered、Version。 - using (var query = new Query(SqlDataSourceEnumerator.Instance.GetDataSources())) + using (var table = SqlDataSourceEnumerator.Instance.GetDataSources()) { - for (int i = 0; i < query.Rows; i++) + var query = new Query(table); + var rows = query.Rows; + list.Capacity = rows; + for (int i = 0; i < rows; i++) { - var item = new SqlServerSource(); + var item = new Source(); item.ServerName = query.Text(i, "ServerName"); list.Add(item); } @@ -483,6 +488,24 @@ COLLATE Chinese_PRC_CI_AS return list.ToArray(); } + /// SQL Server 实例的信息。 + public sealed class Source + { + + /// + public string ServerName { get; set; } + + /// + public string InstanceName { get; set; } + + /// + public string IsClustered { get; set; } + + /// + public string Version { get; set; } + + } + #endif /// 创建参数。 diff --git a/Apewer.Source/Source/SqlServerSouce.cs b/Apewer.Source/Source/SqlServerSouce.cs deleted file mode 100644 index 561caa8..0000000 --- a/Apewer.Source/Source/SqlServerSouce.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Apewer.Source -{ - - /// 枚举的 SQL Server 源。 - public class SqlServerSource - { - - /// - public string ServerName { get; set; } - - /// - public string InstanceName { get; set; } - - /// - public string IsClustered { get; set; } - - /// - public string Version { get; set; } - - } - -} diff --git a/Apewer.Source/Source/Sqlite.cs b/Apewer.Source/Source/Sqlite.cs index 2e71c0e..b794284 100644 --- a/Apewer.Source/Source/Sqlite.cs +++ b/Apewer.Source/Source/Sqlite.cs @@ -1,11 +1,8 @@ -/* 2021.11.07 */ - -using System; +using System; using System.Collections.Generic; using System.Data; -using System.Data.Common; using System.Data.SQLite; -using System.Drawing; +using System.IO; using System.Text; //using Mono.Data.Sqlite; @@ -14,18 +11,26 @@ using static Apewer.Source.SourceUtility; namespace Apewer.Source { - /// 用于快速连接 SQLite 数据库的辅助。 - public sealed class Sqlite : DbClient + /// 连接 SQLite 数据库的客户端。 + public class Sqlite : DbClient { - #region 基础 + #region connection private SQLiteConnection _conn = null; private string _connstr = null; private string _path = null; private string _pass = null; - /// 使用连接字符串创建数据库连接实例。 + /// 连接字符串。 + public override string ConnectionString => _connstr; + + /// 当前数据库的文件路径。 + public string Path { get => _path; } + + /// 使用现有的连接创建实例。 + /// 有效的 SQLite 连接。 + /// 超时设定。 /// /// public Sqlite(IDbConnection connection, Timeout timeout = null) : base(timeout) @@ -40,13 +45,33 @@ namespace Apewer.Source } /// 创建连接实例。 - /// 注意:
- 构造函数不会创建不存在的文件;
- 参数 path 为文件路径,指定为空时将使用 :memory: 作为路径连接内存。
+ /// 数据库的文件路径,指定为空时将使用 :memory: 作为路径。 + /// 连接数据库的密码,使用内存数据库时此参数将被忽略。 + /// 超时设定。 + /// public Sqlite(string path = null, string pass = null, Timeout timeout = null) : base(timeout) { - _path = path.IsEmpty() ? Memory : path; - _pass = pass; - if (pass.IsEmpty()) _connstr = $"data source='{_path}'; version=3; "; - else _connstr = $"data source='{_path}'; password={_pass}; version=3; "; + // 使用内存。 + if (string.IsNullOrEmpty(path) || path.ToLower() == Memory) + { + _connstr = "data source=':memory:'; version=3; "; + _path = Memory; + return; + } + + // 使用文件。 + if (!File.Exists(path)) throw new FileNotFoundException("文件不存在。", path); + _connstr = $"data source='{_path}'; version=3; "; + _path = path; + if (!string.IsNullOrEmpty(pass)) + { + pass = pass.Trim(); + if (!string.IsNullOrEmpty(pass)) + { + _connstr += $"password={_pass}; "; + _pass = pass; + } + } } /// @@ -86,14 +111,14 @@ namespace Apewer.Source #region public - /// + /// public override string[] StoreNames() => throw new NotImplementedException(); /// - public override string[] TableNames() => TextColumn("select name from sqlite_master where type='table' order by name"); + public override string[] TableNames() => QueryStrings("select name from sqlite_master where type='table' order by name"); /// - public override string[] ColumnNames(string tableName) => TextColumn($"pragma table_info('{tableName.SafeName()}'); "); + public override string[] ColumnNames(string tableName) => QueryStrings($"pragma table_info('{tableName.SafeName()}'); "); /// 插入记录。返回错误信息。 public override string Insert(object record, string table = null, bool adjust = true) @@ -244,9 +269,6 @@ namespace Apewer.Source } } - /// - public override string ConnectionString => _connstr; - /// protected override IDataAdapter CreateDataAdapter(IDbCommand command) => new SQLiteDataAdapter((SQLiteCommand)command); @@ -261,21 +283,21 @@ namespace Apewer.Source protected override IDataParameter CreateParameter() => new SQLiteParameter(); /// - protected override string KeysSql(string tableName, string keyField, string flagField, long flagValue) + protected override string Keys(string tableName, string keyField, string flagField, long flagValue) { if (flagValue == 0) return $"select [{keyField}] from [{tableName}] where [{flagField}] = {flagValue}"; return $"select [{keyField}] from [{tableName}]"; } /// - protected override string RecordSql(string tableName, string keyField, string keyValue, string flagField, long flagValue) + protected override string Get(string tableName, string keyField, string keyValue, string flagField, long flagValue) { if (flagValue == 0) return $"select * from [{tableName}] where [{keyField}] = '{keyValue}' limit 1"; else return $"select * from [{tableName}] where [{keyField}] = '{keyValue}' and [{flagField}] = {flagValue} limit 1"; } /// - protected override string RecordsSql(string tableName, string flagField, long flagValue) + protected override string List(string tableName, string flagField, long flagValue) { if (flagValue == 0) return $"select * from [{tableName}]"; else return $"select * from [{tableName}] where [{flagField}] = {flagValue}"; @@ -292,7 +314,7 @@ namespace Apewer.Source public const string Memory = ":memory:"; /// 查询数据库中的所有视图名。 - public string[] ViewNames() => TextColumn("select name from sqlite_master where type='view' order by name"); + public string[] ViewNames() => QueryStrings("select name from sqlite_master where type='view' order by name"); #endregion diff --git a/Apewer/Apewer.csproj b/Apewer/Apewer.csproj index 0578062..4075928 100644 --- a/Apewer/Apewer.csproj +++ b/Apewer/Apewer.csproj @@ -3,6 +3,7 @@ + true netstandard2.0;netcoreapp3.1;net461;net40;net20 diff --git a/Apewer/Apewer.props b/Apewer/Apewer.props index 51702ac..a52e86e 100644 --- a/Apewer/Apewer.props +++ b/Apewer/Apewer.props @@ -9,7 +9,7 @@ Apewer Apewer Libraries - 6.6.28 + 6.7.0 @@ -51,13 +51,11 @@ - + + + + + diff --git a/Apewer/ClockUtility.cs b/Apewer/ClockUtility.cs index c235edc..15c46b2 100644 --- a/Apewer/ClockUtility.cs +++ b/Apewer/ClockUtility.cs @@ -9,14 +9,23 @@ namespace Apewer { /// 时钟。 - public class ClockUtility + public static class ClockUtility { + #region Fixed + + private static DateTime _zero = new DateTime(0L, DateTimeKind.Unspecified); + private static DateTime _origin = NewOrigin(DateTimeKind.Unspecified); + private static DateTime _utc_origin = NewOrigin(DateTimeKind.Utc); + /// 创建新的零值 DateTime 对象。 - public static DateTime Zero { get => new DateTime(0L, DateTimeKind.Utc); } + public static DateTime Zero { get => _zero; } + + /// 获取一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000,表示为本地时间。 + public static DateTime Origin { get => _origin; } - /// 获取一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000。 - public static DateTime Origin { get => new DateTime(1970, 1, 1, 0, 0, 0, 0); } + /// 获取一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000,表示为协调通用时间 (UTC)。 + public static DateTime UtcOrigin { get => _utc_origin; } /// 获取一个 DateTime 对象,该对象设置为此计算机上的当前日期和时间,表示为本地时间。 public static DateTime Now { get => DateTime.Now; } @@ -24,10 +33,28 @@ namespace Apewer /// 获取一个 DateTime 对象,该对象设置为此计算机上的当前日期和时间,表示为协调通用时间 (UTC)。 public static DateTime UtcNow { get => DateTime.UtcNow; } + /// 创建一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000。 + public static DateTime NewOrigin(DateTimeKind kind) => new DateTime(1970, 1, 1, 0, 0, 0, 0, kind); + + #endregion + + #region Clone + + /// 克隆 DateTime 对象,并使用新的 Kind。 + /// 要克隆的 DateTime 对象。 + /// 时间类型。 + /// 克隆后带有新 Kind 的 DateTime 对象。 + public static DateTime Clone(this DateTime dateTime, DateTimeKind kind) + { + return new DateTime(dateTime.Ticks, kind); + } + + #endregion + #region Common /// 判断指定年份是闰年。 - public static bool IsLeapYear(int year) + public static bool IsLeapYear(this int year) { if (year % 400 == 0) return true; if (year % 100 == 0) return false; @@ -105,11 +132,12 @@ namespace Apewer /// 从毫秒时间戳获取 DateTime 对象。发生异常且不允许异常时将返回 1970-01-01 00:00:00.000。 /// - public static DateTime FromStamp(long stamp, bool throwException = true) + public static DateTime FromStamp(long stamp, DateTimeKind kind = DateTimeKind.Unspecified, bool throwException = true) { try { - var datetime = Origin.AddMilliseconds(Convert.ToDouble(stamp)); + var origin = NewOrigin(kind); + var datetime = origin.AddMilliseconds(Convert.ToDouble(stamp)); return datetime; } catch diff --git a/Apewer/Internals/CollectionHelper.cs b/Apewer/CollectionUtility.cs similarity index 59% rename from Apewer/Internals/CollectionHelper.cs rename to Apewer/CollectionUtility.cs index c28052c..d9903f5 100644 --- a/Apewer/Internals/CollectionHelper.cs +++ b/Apewer/CollectionUtility.cs @@ -4,91 +4,166 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Text; -namespace Apewer.Internals +namespace Apewer { - internal class CollectionHelper + /// 集合的实用工具。 + public static class CollectionUtility { - #region 排序。 + #region 判断 - public static List Sort(List list, Func comparison) + /// 判断集合为空。 + public static bool IsEmpty(IEnumerable objects) { - if (list == null) return null; - if (comparison == null) return list; - list.Sort(new Comparison(comparison)); - return list; + if (objects == null) return true; + if (objects is T[]) return ((T[])objects).LongLength < 1L; + if (objects is ICollection) return ((ICollection)objects).Count < 1; + foreach (var item in objects) return false; + return true; } - public static List Ascend(List list) where T : IComparable + /// 判断集合存在元素。 + public static bool NotEmpty(IEnumerable objects) { - if (list == null) return null; - list.Sort((a, b) => a.CompareTo(b)); - return list; + if (objects == null) return false; + if (objects is T[]) return ((T[])objects).LongLength > 0L; + if (objects is ICollection) return ((ICollection)objects).Count > 0; + foreach (var item in objects) return true; + return false; } - public static List Descend(List list) where T : IComparable + /// 获取集合中元素的数量。 + public static int Count(IEnumerable objects) { - if (list == null) return null; - list.Sort((a, b) => -a.CompareTo(b)); - return list; - } + if (objects == null) return 0; - public static Dictionary SortKey(Dictionary dict, Func comparison) - { - if (dict == null) return null; - if (comparison == null) return null; - var list = new List>(dict); - list.Sort(new Comparison>((a, b) => comparison(a.Key, b.Key))); - dict.Clear(); - foreach (var item in list) dict.Add(item.Key, item.Value); - return dict; + var array = objects as T[]; + if (array != null) return array.Length; + + var collection = objects as ICollection; + if (collection != null) return collection.Count; + + var count = 0; + foreach (var cell in objects) count++; + return count; } - public static Dictionary SortValue(Dictionary dict, Func comparison) + /// 检查集合是否包含 item。 + public static bool Contains(IEnumerable objects, T cell) { - if (dict == null) return null; - if (comparison == null) return dict; - var list = new List>(dict); - list.Sort(new Comparison>((a, b) => comparison(a.Value, b.Value))); - dict.Clear(); - foreach (var item in list) dict.Add(item.Key, item.Value); - return dict; - } + if (objects == null) return false; - #endregion + // objects 实现了含有 Contains 方法的接口。 + if (objects is ICollection) return ((ICollection)objects).Contains(cell); - public static T First(IEnumerable collection, T failed = default(T)) + // cell 无效。 + if (cell == null) + { + foreach (var i in objects) + { + if (i == null) return true; + } + return false; + } + + // cell 有效,进行默认比较。 + var comparer = EqualityComparer.Default; + foreach (var i in objects) + { + if (comparer.Equals(i, cell)) return true; + } + return false; + } + + /// 获取 item 在集合中的偏移位置,不存在时返回 -1。 + public static int IndexOf(IEnumerable objects, T item) { - if (collection == null) return failed; + if (objects == null) return -1; + if (objects is IList list) return list.IndexOf(item); - var array = collection as T[]; - if (array != null) return array.Length > 0 ? array[0] : failed; + if (item == null) + { + if (objects is T[] array) + { + var length = array.Length; + for (var i = 0; i < length; i++) + { + if (array[i] == null) return i; + } + return -1; + } - var list = collection as IList; - if (list != null) return list.Count > 0 ? list[0] : failed; + var index = 0; + foreach (var obj in objects) + { + if (obj == null) return index; + index++; + } + return -1; + } + else + { + var comparer = EqualityComparer.Default; - foreach (var item in collection) return item; - return failed; + if (objects is T[] array) + { + var length = array.Length; + for (var i = 0; i < length; i++) + { + if (comparer.Equals(item, array[i])) return i; + } + return -1; + } + + var index = 0; + foreach (var obj in objects) + { + if (comparer.Equals(item, obj)) return index; + index++; + } + return -1; + } } - public static T Last(IEnumerable collection, T failed = default(T)) - { - if (collection == null) return failed; + #endregion - var array = collection as T[]; - if (array != null) return array.Length > 0 ? array[array.Length - 1] : failed; + #region 类型转换 - var list = collection as IList; - if (list != null) return list.Count > 0 ? list[list.Count - 1] : failed; + /// 转换模型类型。 + public static TDst[] As(this TSrc[] array) where TDst : class + { + if (array == null) return null; + var count = array.Length; + var output = new TDst[count]; + for (var i = 0; i < count; i++) + { + var item = array[i]; + if (item == null) continue; + output[i] = item as TDst; + } + return output; + } - var value = failed; - foreach (var item in collection) value = item; - return value; + /// 转换模型类型。 + public static TDst[] As(this TSrc[] array, Func convert) + { + if (convert == null) throw new ArgumentNullException(nameof(convert)); + + if (array == null) return null; + var count = array.Length; + var output = new TDst[count]; + for (var i = 0; i < count; i++) + { + var item = array[i]; + if (item == null) continue; + output[i] = convert(item); + } + return output; } - // 安全转换为 List 对象。可指定排除 NULL 值元素。 - public static List ToList(IEnumerable objects, bool excludeNull = false) + /// 安全转换为 List<> 对象。可指定排除 NULL 值元素。 + public static List List(IEnumerable objects, bool excludeNull = false) { if (objects == null) return new List(); @@ -122,8 +197,8 @@ namespace Apewer.Internals return list; } - // 安全转换为 T[] 对象。可指定排除 NULL 值元素。 - public static T[] ToArray(IEnumerable objects, bool excludeNull = false) + /// 安全转换为 <>[] 对象。可指定排除 NULL 值元素。 + public static T[] Array(IEnumerable objects, bool excludeNull = false) { if (objects == null) return new T[0]; if (objects is T[]) return (T[])objects; @@ -148,7 +223,7 @@ namespace Apewer.Internals { capacity += group; var temp = new T[capacity]; - Array.Copy(array, 0, temp, 0, added); + System.Array.Copy(array, 0, temp, 0, added); array = temp; } array[added] = item; @@ -156,115 +231,84 @@ namespace Apewer.Internals } if (added < 1 || added == capacity) return array; var collapsed = new T[added]; - Array.Copy(array, 0, collapsed, 0, added); + System.Array.Copy(array, 0, collapsed, 0, added); return collapsed; } } - public static bool IsEmpty(IEnumerable objects) + /// 生成 StringPairs 对象实例为副本。 + public static StringPairs StringPairs(NameValueCollection @this) => Apewer.StringPairs.From(@this); + + /// 转换集合为数组。 + /// + /// + public static Dictionary Dictionary(NameValueCollection collection) { - if (objects == null) return true; - if (objects is T[]) return ((T[])objects).LongLength == 0; - if (objects is ICollection) return ((ICollection)objects).Count == 0; - foreach (var i in objects) return false; - return true; + if (collection == null) return null; + var count = collection.Count; + var dict = new Dictionary(); + for (var i = 0; i < count; i++) + { + var key = collection.GetKey(i); + var values = collection.GetValues(i); + dict.Add(key, values); + } + return dict; } - public static bool NotEmpty(IEnumerable objects) => !IsEmpty(objects); - - public static int IndexOf(IEnumerable objects, T item) + /// 转换集合为字典。 + /// 字典 Key 的类型。 + /// 字典 Value 的类型。 + /// 要转换的集合。 + /// 根据元素获取 Key 的函数。 + /// + public static Dictionary Dictionary(IEnumerable items, Func key) { - if (objects == null) return -1; - if (objects is IList list) return list.IndexOf(item); - - if (item == null) - { - if (objects is T[] array) - { - var length = array.Length; - for (var i = 0; i < length; i++) - { - if (array[i] == null) return i; - } - return -1; - } + if (items == null) throw new ArgumentNullException(nameof(items)); + if (key == null) throw new ArgumentNullException(nameof(key)); - var index = 0; - foreach (var obj in objects) - { - if (obj == null) return index; - index++; - } - return -1; - } - else + var dict = new Dictionary(); + foreach (var i in items) { - var comparer = EqualityComparer.Default; + if (i == null) continue; - if (objects is T[] array) - { - var length = array.Length; - for (var i = 0; i < length; i++) - { - if (comparer.Equals(item, array[i])) return i; - } - return -1; - } - - var index = 0; - foreach (var obj in objects) - { - if (comparer.Equals(item, obj)) return index; - index++; - } - return -1; + var k = key(i); + if (k.IsNull()) continue; + if (dict.ContainsKey(k)) continue; + dict.Add(k, i); } + return dict; } - // 判断集合包含特定值。 - public static bool Contains(IEnumerable objects, T cell) - { - if (objects == null) return false; + #endregion - // objects 实现了含有 Contains 方法的接口。 - if (objects is ICollection) return ((ICollection)objects).Contains(cell); + #region 修改集合 - // cell 无效。 - if (cell == null) - { - foreach (var i in objects) - { - if (i == null) return true; - } - return false; - } + /// 添加多个元素。 + public static void Add(List list, params T[] items) + { + if (list != null && items != null) list.AddRange(items); + } - // cell 有效,进行默认比较。 - var comparer = EqualityComparer.Default; - foreach (var i in objects) + /// 添加多个元素。 + public static IList Add(IList list, IEnumerable items) + { + if (list != null && items != null) { - if (comparer.Equals(i, cell)) return true; + foreach (var item in items) list.Add(item); } - return false; + return list; } - // 获取集合中元素的数量。 - public static int Count(IEnumerable objects) + /// 添加元素。 + public static bool Add(IList> list, TKey key, TValue value) { - if (objects == null) return 0; - - var array = objects as T[]; - if (array != null) return array.Length; - - var collection = objects as ICollection; - if (collection != null) return collection.Count; - - var count = 0; - foreach (var cell in objects) count++; - return count; + if (list == null) return false; + list.Add(new KeyValuePair(key, value)); + return true; } - // 对元素去重,且去除 NULL 值。 + /// 对元素去重,且去除 NULL 值。 public static T[] Distinct(IEnumerable items) { if (items == null) throw new ArgumentNullException(nameof(items)); @@ -295,13 +339,20 @@ namespace Apewer.Internals if (added < count) { var temp = new T[added]; - Array.Copy(array, 0, temp, 0, added); + System.Array.Copy(array, 0, temp, 0, added); array = temp; } + return array; } - // 获取可枚举集合的部分元素。 + /// 获取可枚举集合的部分元素。 + /// 集合元素的类型。 + /// 原集合。 + /// 在集合前段要跳过的元素数量。 + /// 要获取的元素数量,指定为负数时不限元素数量。 + /// 填充器,获取范围超出原集合的部分,使用此方法填充元素;此函数默认返回 的默认值。 + /// 数量符合 count 的数组。 public static T[] Slice(IEnumerable objects, int skip = 0, int count = -1, Func stuffer = null) { if (count == 0) return new T[0]; @@ -318,6 +369,7 @@ namespace Apewer.Internals if (count > 0 && added == count) return ab.Export(); } } + if (objects != null) { var offset = 0; @@ -334,45 +386,161 @@ namespace Apewer.Internals if (count > 0 && added == count) return ab.Export(); } } + while (added < count) { ab.Add(stuffer == null ? default : stuffer()); added++; } + return ab.Export(); } - public static IList Add(IList list, IEnumerable items) + #endregion + + #region 排序 + + /// 对列表中的元素排序。 + public static List Sort(List list, Func comparison) { - if (list != null && items != null) - { - foreach (var item in items) list.Add(item); - } + if (list == null) return null; + if (comparison == null) return list; + list.Sort(new Comparison(comparison)); return list; } - public static bool Add(IList> list, TKey key, TValue value) + /// 对数组排序。 + /// + public static T[] Sort(T[] array, Func comparison) { - if (list == null) return false; - list.Add(new KeyValuePair(key, value)); - return true; + if (array == null) return array; + if (comparison == null) return array; + System.Array.Sort(array, new Comparison(comparison)); + return array; } - internal static Dictionary Dict(NameValueCollection collection) + /// 获取集合中的第一个元素。可指定失败时的默认返回值。 + public static T First(IEnumerable collection, T failed = default(T)) { - if (collection == null) return null; - var count = collection.Count; - var dict = new Dictionary(); - for (var i = 0; i < count; i++) - { - var key = collection.GetKey(i); - var values = collection.GetValues(i); - dict.Add(key, values); - } + if (collection == null) return failed; + + var array = collection as T[]; + if (array != null) return array.Length > 0 ? array[0] : failed; + + var list = collection as IList; + if (list != null) return list.Count > 0 ? list[0] : failed; + + foreach (var item in collection) return item; + return failed; + } + + /// 获取集合中的最后一个元素。可指定失败时的默认返回值。 + public static T Last(IEnumerable collection, T failed = default(T)) + { + if (collection == null) return failed; + + var array = collection as T[]; + if (array != null) return array.Length > 0 ? array[array.Length - 1] : failed; + + var list = collection as IList; + if (list != null) return list.Count > 0 ? list[list.Count - 1] : failed; + + var value = failed; + foreach (var item in collection) value = item; + return value; + } + + /// 对数组升序排序。 + /// + public static T[] Ascend(T[] array) where T : IComparable + { + if (array == null) return null; + array.Sort((a, b) => a.CompareTo(b)); + return array; + } + + /// 对数组升序排序。 + /// + public static List Ascend(List list) where T : IComparable + { + if (list == null) return null; + list.Sort((a, b) => a.CompareTo(b)); + return list; + } + + /// 对数组升序排序。 + /// + public static void Ascend(T[] array, Func func) where TProp : IComparable + { + if (array != null && func != null) Sort(array, (a, b) => func(a).CompareTo(func(b))); + } + + /// 对数组降序排序。 + /// + public static T[] Descend(T[] array) where T : IComparable + { + if (array == null) return array; + Sort(array, (a, b) => 0 - a.CompareTo(b)); + return array; + } + + /// 对数组降序排序。 + /// + public static List Descend(List list) where T : IComparable + { + if (list == null) return null; + list.Sort((a, b) => -a.CompareTo(b)); + return list; + } + + /// 对数组降序排序。 + /// + public static void Descend(T[] array, Func func) where TProp : IComparable + { + if (array != null && func != null) Sort(array, (a, b) => 0 - func(a).CompareTo(func(b))); + } + + /// 对字典中的键排序。 + public static Dictionary SortKey(Dictionary dict, Func comparison) + { + if (dict == null) return null; + if (comparison == null) return null; + var list = new List>(dict); + list.Sort(new Comparison>((a, b) => comparison(a.Key, b.Key))); + dict.Clear(); + foreach (var item in list) dict.Add(item.Key, item.Value); return dict; } - public static object[] ParseParams(object cells) + /// 对字典中的键排序。 + public static Dictionary SortKey(Dictionary @this) where TKey : IComparable + { + return SortKey(@this, (a, b) => a.CompareTo(b)); + } + + /// 对字典中的值排序。 + public static Dictionary SortValue(Dictionary dict, Func comparison) + { + if (dict == null) return null; + if (comparison == null) return dict; + var list = new List>(dict); + list.Sort(new Comparison>((a, b) => comparison(a.Value, b.Value))); + dict.Clear(); + foreach (var item in list) dict.Add(item.Key, item.Value); + return dict; + } + + /// 对字典中的值排序。 + public static Dictionary SortValue(Dictionary @this) where TValue : IComparable + { + return SortValue(@this, (a, b) => a.CompareTo(b)); + } + + #endregion + + #region params + + internal static object[] ParseParams(object cells) { var parsed = new ArrayBuilder(); ParseParams(cells, parsed, 1); @@ -407,6 +575,8 @@ namespace Apewer.Internals parsed.Add(cells.ToString()); } + #endregion + } } diff --git a/Apewer/Externals/Newtonsoft.Json-11.0.1/Utilities/LinqBridge.cs b/Apewer/Externals/Newtonsoft.Json-11.0.1/Utilities/LinqBridge.cs index e7e68cd..2a957a6 100644 --- a/Apewer/Externals/Newtonsoft.Json-11.0.1/Utilities/LinqBridge.cs +++ b/Apewer/Externals/Newtonsoft.Json-11.0.1/Utilities/LinqBridge.cs @@ -3030,29 +3030,6 @@ namespace Newtonsoft.Json.Utilities.LinqBridge } } -namespace Newtonsoft.Json.Serialization -{ -#pragma warning disable 1591 - internal delegate TResult Func(); - - internal delegate TResult Func(T a); - - internal delegate TResult Func(T1 arg1, T2 arg2); - - internal delegate TResult Func(T1 arg1, T2 arg2, T3 arg3); - - internal delegate TResult Func(T1 arg1, T2 arg2, T3 arg3, T4 arg4); - - internal delegate void Action(); - - internal delegate void Action(T1 arg1, T2 arg2); - - internal delegate void Action(T1 arg1, T2 arg2, T3 arg3); - - internal delegate void Action(T1 arg1, T2 arg2, T3 arg3, T4 arg4); -#pragma warning restore 1591 -} - namespace System.Runtime.CompilerServices { /// diff --git a/Apewer/Externals/System/Action.cs b/Apewer/Externals/System/Action.cs new file mode 100644 index 0000000..72142f4 --- /dev/null +++ b/Apewer/Externals/System/Action.cs @@ -0,0 +1,36 @@ +#if NET20 + +using System; +using System.Collections.Generic; +using System.Text; + +namespace System +{ + + /// 封装一个方法,该方法不具有参数且不返回值。 + public delegate void Action(); + + /// 封装一个方法,该方法不具有参数且不返回值。 + public delegate void Action(T1 arg1, T2 arg2); + + /// 封装一个方法,该方法不具有参数且不返回值。 + public delegate void Action(T1 arg1, T2 arg2, T3 arg3); + + /// 封装一个方法,该方法不具有参数且不返回值。 + public delegate void Action(T1 arg1, T2 arg2, T3 arg3, T4 arg4); + + /// 封装一个方法,该方法不具有参数且不返回值。 + public delegate void Action(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); + + /// 封装一个方法,该方法不具有参数且不返回值。 + public delegate void Action(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); + + /// 封装一个方法,该方法不具有参数且不返回值。 + public delegate void Action(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); + + /// 封装一个方法,该方法不具有参数且不返回值。 + public delegate void Action(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); + +} + +#endif diff --git a/Apewer/Externals/System/EventHandler.cs b/Apewer/Externals/System/EventHandler.cs new file mode 100644 index 0000000..64d5d2b --- /dev/null +++ b/Apewer/Externals/System/EventHandler.cs @@ -0,0 +1,18 @@ +#if NET20 + +using System; +using System.Collections.Generic; +using System.Text; + +namespace System +{ + + // /// 表示当事件提供数据时将处理该事件的方法。 + // /// 事件生成的事件数据的类型。 + // /// 事件源。 + // /// 包含事件数据的对象。 + // public delegate void EventHandler(object sender, TEventArgs e); + +} + +#endif diff --git a/Apewer/Externals/System/Func.cs b/Apewer/Externals/System/Func.cs new file mode 100644 index 0000000..b2518d1 --- /dev/null +++ b/Apewer/Externals/System/Func.cs @@ -0,0 +1,33 @@ +#if NET20 + +using System; +using System.Collections.Generic; +using System.Text; + +namespace System +{ + + /// 封装一个方法,该方法不具有参数,且返回由 TResult 参数指定的类型的值。 + /// 此委托封装的方法的返回值类型。 + /// 此委托封装的方法的返回值。 + public delegate TResult Func(); + + /// 封装一个方法,该方法具有一个参数,且返回由 TResult 参数指定的类型的值。 + /// 此委托封装的方法的参数类型。 + /// 此委托封装的方法的返回值类型。 + /// 此委托封装的方法的参数。 + /// 此委托封装的方法的返回值。 + public delegate TResult Func(T arg); + + /// 封装一个方法,该方法具有两个参数,并返回由 TResult 参数指定的类型的值。 + /// 此委托封装的方法的第一个参数的类型。 + /// 此委托封装的方法的第二个参数的类型。 + /// 此委托封装的方法的返回值类型。 + /// 此委托封装的方法的第一个参数。 + /// 此委托封装的方法的第二个参数。 + /// 此委托封装的方法的返回值。 + public delegate TResult Func(T1 arg1, T2 arg2); + +} + +#endif diff --git a/Apewer/Externals/System/Linq/Enumerable.cs b/Apewer/Externals/System/Linq/Enumerable.cs new file mode 100644 index 0000000..437ef76 --- /dev/null +++ b/Apewer/Externals/System/Linq/Enumerable.cs @@ -0,0 +1,42 @@ +#if NET20 + +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.Linq +{ + + /// + public static class Enumerable + { + + /// + public static List ToList(this IEnumerable items) + { + var list = new List(); + foreach (var item in list) list.Add(item); + return list; + } + + /// + public static T[] ToArray(this IEnumerable items) + { + return ToList(items).ToArray(); + } + + /// + public static List Select(this IEnumerable items, Func selector) + { + if (items == null) return new List(); + + var list = new List(); + foreach (var item in items) list.Add(selector.Invoke(item)); + return list; + } + + } + +} + +#endif diff --git a/Apewer/Json.cs b/Apewer/Json.cs index 382bdce..2ba0525 100644 --- a/Apewer/Json.cs +++ b/Apewer/Json.cs @@ -1461,7 +1461,7 @@ namespace Apewer else if (entity is IDictionary asExpando) { return From(new Dictionary(asExpando), lower, depth, force); } else if (entity is IDictionary) { return From(entity as IDictionary, lower, depth, force); } else if (entity is IList) { return From(entity as IList, lower, depth, force); } - else if (entity is NameValueCollection) { return From(CollectionHelper.Dict(entity as NameValueCollection), lower, depth, force); } + else if (entity is NameValueCollection nc) { return From(CollectionUtility.Dictionary(nc), lower, depth, force); } var type = entity.GetType(); var independent = RuntimeUtility.Contains(type); diff --git a/Apewer/Network/Extension.cs b/Apewer/Network/Extension.cs index fa146e9..d705c50 100644 --- a/Apewer/Network/Extension.cs +++ b/Apewer/Network/Extension.cs @@ -1,5 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; using System.Text; namespace Apewer.Network @@ -35,6 +39,123 @@ namespace Apewer.Network return MailMethods.Send(value, sender, receiver, content, title); } + /// 生成 Json 数组。 + static Json ToJsonArray(this IEnumerable items, Func serializer) + { + if (items == null) return null; + + var array = Apewer.Json.NewArray(); + if (serializer != null) + { + foreach (var item in items) + { + if (item == null) continue; + var json = serializer.Invoke(item); + array.AddItem(json); + } + } + return array; + } + + /// 生成 Json 对象。 + public static Json ToJson(this IPAddress address) + { + if (address == null) return null; + var json = Json.NewObject(); + json.SetProperty("text", address.ToString()); + json.SetProperty("family", address.AddressFamily.ToString()); + if (address.AddressFamily == AddressFamily.InterNetworkV6) + { + json.SetProperty("isLinkLocal", address.IsIPv6LinkLocal); + json.SetProperty("scopeId", address.ScopeId); + } + return json; + } + + /// 生成 Json 数组。 + public static Json ToJson(this IEnumerable addresses) => ToJsonArray(addresses, ToJson); + + /// 生成 Json 数组。 + public static Json ToJson(this NetworkInterface @interface) + { + if (@interface == null) return null; + var item = @interface; + + var json = Json.NewObject(); + json.SetProperty("text", item.ToString()); + + json.SetProperty("description", item.Description); + json.SetProperty("id", item.Id); + json.SetProperty("isReceiveOnly", item.IsReceiveOnly); + json.SetProperty("name", item.Name); + json.SetProperty("type", item.NetworkInterfaceType.ToString()); + json.SetProperty("operational", item.OperationalStatus.ToString()); + json.SetProperty("speed", item.Speed); + json.SetProperty("multicast", item.SupportsMulticast); + + json.SetProperty("mac", item.GetPhysicalAddress().ToString()); + json.SetProperty("ipProperties", ToJson(item.GetIPProperties())); + + return json; + } + + /// 生成 Json 数组。 + public static Json ToJson(this IEnumerable items) => ToJsonArray(items, ToJson); + + /// 生成 Json 对象。 + public static Json ToJson(this IPInterfaceProperties properties) + { + if (properties == null) return null; + var json = Json.NewObject(); + json.SetProperty("anycast", ToJson(properties.AnycastAddresses.Select(x => x.Address))); + json.SetProperty("dhcp", ToJson(properties.DhcpServerAddresses)); + json.SetProperty("dns", ToJson(properties.DnsAddresses)); + json.SetProperty("suffix", properties.DnsSuffix); + json.SetProperty("gateway", ToJson(properties.GatewayAddresses.Select(x => x.Address))); + json.SetProperty("multicast", ToJson(properties.MulticastAddresses.Select(x => x.Address))); + json.SetProperty("unicast", ToJson(properties.UnicastAddresses.Select(x => x.Address))); + json.SetProperty("wins", ToJson(properties.WinsServersAddresses)); + return json; + } + + /// 生成 Json 对象。 + public static Json ToJson(this IPAddressInformation information) + { + if (information == null) return null; + var json = Json.NewObject(); + json.SetProperty("address", ToJson(information.Address)); + json.SetProperty("isDnsEligible", information.IsDnsEligible); + json.SetProperty("isTransient", information.IsTransient); + return json; + } + + /// 生成 Json 对象。 + public static Json ToJson(this PingReply reply) + { + if (reply == null) return null; + + var buffer = reply.Buffer.X2(); + if (buffer.Replace("0", "").IsEmpty()) buffer = null; + + var json = Json.NewObject(); + json.SetProperty("address", ToJson(reply.Address)); + if (buffer != null) json.SetProperty("buffer", buffer); + json.SetProperty("options", ToJson(reply.Options)); + json.SetProperty("roundtripTime", reply.RoundtripTime); + json.SetProperty("status", reply.Status.ToString()); + return json; + } + + /// 生成 Json 对象。 + public static Json ToJson(this PingOptions options) + { + if (options == null) return null; + var json = Json.NewObject(); + json.SetProperty("dontFragment", options.DontFragment); + json.SetProperty("ttl", options.Ttl); + return json; + } + } } diff --git a/Apewer/Network/HttpClient.cs b/Apewer/Network/HttpClient.cs index d0ee3b8..326b93b 100644 --- a/Apewer/Network/HttpClient.cs +++ b/Apewer/Network/HttpClient.cs @@ -74,7 +74,8 @@ namespace Apewer.Network /// 默认值:False public bool AllowRedirect { get; set; } - /// 获取或设置要写入响应主体的流。 + /// 获取或设置要接收响应主体的流。默认为 NULL 值。 + /// 指定为 NULL 时,响应体将写入字节数组;
非 NULL 时,响应体将写入此流,并忽略 ResponseData 属性。
public Stream ResponseStream { get; set; } /// 获取或设置读取响应主体的进度回调 @@ -287,8 +288,9 @@ namespace Apewer.Network foreach (var key in response.Headers.AllKeys) { var values = response.Headers.GetValues(key); - foreach (var value in values) headers.Add(key, value); + headers.Add(key, values.Join(",")); } + ResponseHeaders = headers; var cb = new ArrayBuilder(); foreach (var item in response.Cookies) diff --git a/Apewer/Network/Icmp.cs b/Apewer/Network/Icmp.cs deleted file mode 100644 index 94d37e0..0000000 --- a/Apewer/Network/Icmp.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Net.NetworkInformation; -using Apewer; - -namespace Apewer.Network -{ - - /// ICMP。 - public class Icmp - { - - /// 发送 PING 命令,命令中包含 32 位零数据。 - /// 目标地址。 - /// 等待响应的超时时间(毫秒)。 - /// 命令的起始 TTL 值(在丢弃数据之前可以转发该数据的路由节点数)。 - /// 是否分段。 - /// 命令的返回结果。 - public static Icmp Ping(string ip, int timeout = 1000, byte ttl = 255, bool df = true) - { - var icmp = new Icmp(); - if (!string.IsNullOrEmpty(ip) && (timeout > 0)) - { - var vip = NetworkUtility.IsIP(ip) ? ip : NetworkUtility.Resolve(ip); - if (vip.Contains(",")) vip = vip.Split(',')[0]; - var op = new Ping(); - var oo = new PingOptions(); - oo.DontFragment = df; - oo.Ttl = ttl; - byte[] bs = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - var or = op.Send((string)vip, NumberUtility.Restrict(timeout, 1, ushort.MaxValue), bs, oo); - icmp._success = (or.Status == IPStatus.Success) ? true : false; - icmp._address = or.Address?.ToString(); - icmp._time = or.RoundtripTime; - icmp._ttl = or.Options == null ? -1 : or.Options.Ttl; - } - return icmp; - } - - private bool _success = false; - private string _address = ""; - private long _time = 0; - private int _ttl = 0; - - /// 构造函数。 - public Icmp() { } - - /// 已成功获取目标的返回。 - public bool Success { get { return _success; } } - - /// 返回的目标地址。 - public string Addresss { get { return _address; } } - - /// 收到返回所经历的时间(毫秒)。 - public long Time { get { return _time; } } - - /// 返回的 TTL 值(在丢弃数据之前可以转发该数据的路由节点数)。 - public int Ttl { get { return _ttl; } } - - } - -} diff --git a/Apewer/NetworkUtility.cs b/Apewer/NetworkUtility.cs index 1cd06d8..5832e55 100644 --- a/Apewer/NetworkUtility.cs +++ b/Apewer/NetworkUtility.cs @@ -296,6 +296,37 @@ namespace Apewer #endregion + #region ICMP + + /// 发送 PING 命令,命令中包含 32 位零数据。 + /// 目标地址。 + /// 等待响应的超时时间(毫秒)。 + /// 命令的起始 TTL 值(在丢弃数据之前可以转发该数据的路由节点数)。 + /// 是否分段。 + /// 命令的返回结果。 + /// + /// + /// + public static PingReply Ping(string address, int timeout = 1000, byte ttl = 255, bool df = true) + { + if (string.IsNullOrEmpty(address)) throw new ArgumentNullException(nameof(address)); + if (timeout < 1) throw new ArgumentOutOfRangeException(nameof(timeout)); + + var ip = IsIP(address) ? address : Resolve(address); + if (ip.Contains(",")) ip = ip.Split(',')[0]; + + var options = new PingOptions(); + options.DontFragment = df; + options.Ttl = ttl; + + var buffer = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + var ping = new Ping(); + var reply = ping.Send(ip, timeout, buffer, options); + return reply; + } + + #endregion + #region HTTP /// 解析 HTTP 方法。 diff --git a/Apewer/Result.cs b/Apewer/Result.cs deleted file mode 100644 index 02eda65..0000000 --- a/Apewer/Result.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Apewer.Internals; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Apewer -{ - - /// 结果状态,Code 为零时表示正常,Code 0 与 NULL 相等。 - [Serializable] - public class Result - { - - private int _code; - private string _message; - - /// 代码。 - public int Code { get => _code; protected set => _code = value; } - - /// 消息。 - public string Message { get => _message; protected set => _message = value; } - - /// 创建实例:Code = 0,Message = NULL。 - public Result() { } - - /// 创建实例。 - public Result(int code, string message = null) - { - _code = code; - _message = message; - } - - /// 创建实例:Code = 0。 - public Result(string message, int code = 0) - { - _code = code; - _message = message; - } - - /// - public override string ToString() - { - if (string.IsNullOrEmpty(_message)) return Code.ToString(); - if (Code == 0) return _message; - return Code.ToString() + "|" + _message; - } - - #region 运算符。 - - /// 获取 Code。 - public static implicit operator int(Result result) => result == null ? 0 : result._code; - - /// ToString - public static implicit operator string(Result result) => result == null ? null : result.ToString(); - - #endregion - - } - - /// 装箱返回结果,T 不适用于 System.String。 - [Serializable] - public class Result : Result, IDisposable - { - - private T _value; - private bool _has = false; - - /// 对象。 - public T Value { get => _value; set => Set(value); } - - /// 含有实体对象。 - public bool HasValue { get => _has; } - - /// 执行与释放或重置非托管资源关联的应用程序定义的任务。 - public void Dispose() => RuntimeUtility.Dispose(Value); - - /// 创建实例:Code = 0,Message = NULL,Value = Default。 - public Result() { } - - /// 创建实例:Code = 0,Message = NULL。 - public Result(T value) => Set(value); - - /// 创建实例:Value = Default。 - public Result(string message, int code = 0) : base(message, code) { } - - /// 创建实例:Value = Default。 - public Result(int code, string message = null) : base(code, message) { } - - /// 创建实例:Value = Default。 - public Result(Exception exception, int code = 0) : base(RuntimeUtility.Message(exception), code) { } - - /// 创建实例:Value = Default。 - public Result(int code, Exception exception = null) : base(code, RuntimeUtility.Message(exception)) { } - - private void Set(T value) - { - _value = value; - _has = typeof(T).IsValueType ? true : (value != null); - } - - #region 运算符。 - - /// 含有实体对象。 - public static implicit operator bool(Result result) => result == null ? false : result._has; - - #endregion - - #region 扩展方法。 - - internal static Result As(Result source) where TDst : TSrc - { - if (source == null) return null; - var destination = new Result(); - destination.Code = source.Code; - destination.Message = source.Message; - if (source._has) - { - var maybe = source._value is TDst; - try { destination.Set((TDst)source._value); } catch { } - } - return destination; - } - - #endregion - - } - -} diff --git a/Apewer/RuntimeUtility.cs b/Apewer/RuntimeUtility.cs index 52a0740..88a1175 100644 --- a/Apewer/RuntimeUtility.cs +++ b/Apewer/RuntimeUtility.cs @@ -372,7 +372,11 @@ namespace Apewer public static bool IsInherits(Type child, Type @base) { // 检查参数。 - if (child == null || @base == null || child == @base) return false; + if (child == null || @base == null) return false; + if (child == @base) return false; + + // 检查 interface 类型。 + if (@base.IsInterface) return @base.IsAssignableFrom(child); // 忽略 System.Object。 var quantum = typeof(object); @@ -463,6 +467,27 @@ namespace Apewer return list.ToArray(); } + /// 是匿名类型。 + /// + public static bool IsAnonymousType(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + + // 类型是由编译器生成。 + if (!Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false)) return false; + + // 是泛型。 + if (!type.IsGenericType) return false; + + // 名称。 + if (!type.Name.StartsWith("<>") || !type.Name.Contains("AnonymousType")) return false; + + // 私有。 + if (type.IsPublic) return false; + + return true; + } + #endregion #region Collect & Dispose diff --git a/Apewer/Source/ColumnAttribute.cs b/Apewer/Source/ColumnAttribute.cs index 8802333..fa9308c 100644 --- a/Apewer/Source/ColumnAttribute.cs +++ b/Apewer/Source/ColumnAttribute.cs @@ -116,17 +116,13 @@ namespace Apewer.Source } /// 到 Boolean 的隐式转换,判断 有效。 - public static implicit operator bool(ColumnAttribute instance) - { - if (instance == null) return false; - return true; - } + public static implicit operator bool(ColumnAttribute instance) => instance != null; /// 解析列特性。 /// 注意:此方法不再抛出异常,当不存在正确的列特性时将返回 NULL 值 - public static ColumnAttribute Parse(Type type, PropertyInfo property, TableAttribute ta) + public static ColumnAttribute Parse(PropertyInfo property, bool force = false) { - if (type == null || property == null || ta == null) return null; + if (property == null) return null; // 属性带有 Independent 特性。 if (property.Contains()) return null; @@ -137,7 +133,7 @@ namespace Apewer.Source var cas = property.GetCustomAttributes(typeof(ColumnAttribute), false); if (cas.LongLength < 1L) { - if (!ta.AllProperties) return null; + if (!force) return null; ca = new ColumnAttribute(); } else ca = (ColumnAttribute)cas[0]; @@ -249,6 +245,8 @@ namespace Apewer.Source return sorted.ToArray(); } + internal void SetPrimaryKey() => _primarykey = true; + } } diff --git a/Apewer/Source/IDbOrm.cs b/Apewer/Source/IDbOrm.cs index b1cce4b..8c4582f 100644 --- a/Apewer/Source/IDbOrm.cs +++ b/Apewer/Source/IDbOrm.cs @@ -34,12 +34,20 @@ namespace Apewer.Source /// 目标记录的类型。 /// 要执行的 SQL 语句。 /// 为 SQL 命令提供参数。 - public Result Query(Type model, string sql, IEnumerable parameters = null); + /// + /// + /// + /// + public object[] Query(Type model, string sql, IEnumerable parameters = null); /// 使用指定语句查询,获取查询结果。 /// 要执行的 SQL 语句。 /// 为 SQL 命令提供参数。 - public Result Query(string sql, IEnumerable parameters = null) where T : class, new(); + /// + /// + /// + /// + public T[] Query(string sql, IEnumerable parameters = null) where T : class, new(); #endregion @@ -55,31 +63,46 @@ namespace Apewer.Source /// 获取指定类型的主键,按 Flag 属性筛选。 /// 要查询的类型。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Keys(Type model, long flag = 0); + /// + /// + /// + public string[] Keys(Type model, long flag = 0); /// 获取指定类型的主键,按 Flag 属性筛选。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Keys(long flag = 0) where T : class, IRecord, new(); + /// + /// + public string[] Keys(long flag = 0) where T : class, IRecord, new(); /// 获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。 /// 目标记录的类型。 /// 目标记录的主键。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Record(Type model, string key, long flag = 0); + /// + /// + /// + public object Get(Type model, string key, long flag = 0); /// 获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。 /// 目标记录的主键。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Record(string key, long flag = 0) where T : class, IRecord, new(); + /// + /// + public T Get(string key, long flag = 0) where T : class, IRecord, new(); /// 查询所有记录。 /// 目标记录的类型。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Records(Type model, long flag = 0); + /// + /// + /// + public object[] List(Type model, long flag = 0); /// 查询所有记录。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Records(long flag = 0) where T : class, IRecord, new(); + /// + /// + public T[] List(long flag = 0) where T : class, IRecord, new(); #endregion diff --git a/Apewer/Source/IRecordPrimaryKey.cs b/Apewer/Source/IRecordPrimaryKey.cs new file mode 100644 index 0000000..b27cdf4 --- /dev/null +++ b/Apewer/Source/IRecordPrimaryKey.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Source +{ + + /// 此记录的 Key 属性作为主键。 + public interface IRecordPrimaryKey : IRecord { } + +} diff --git a/Apewer/Source/IndexAttribute.cs b/Apewer/Source/IndexAttribute.cs deleted file mode 100644 index 416bd05..0000000 --- a/Apewer/Source/IndexAttribute.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Apewer.Source -{ - - /// 表示此表拥有索引,此特性不被继承。 - [Serializable] - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public sealed class IndexAttribute : Attribute - { - - string _name = null; - string _sql = null; - - /// 索引名称。 - public string Name { get; } - - /// 用于创建索引的 SQL 语句。 - public string SqlStatement { get; set; } - - /// 声明索引。 - /// 索引名称。 - /// 用于创建此索引的 SQL 语句。 - public IndexAttribute(string name, string sqlStatement) - { - _name = name.ToTrim(); - _sql = sqlStatement.ToTrim(); - } - - /// 到 Boolean 的隐式转换,判断 有效。 - public static implicit operator bool(IndexAttribute instance) - { - if (instance == null) return false; - if (instance._name.IsEmpty()) return false; - if (instance._sql.IsEmpty()) return false; - return true; - } - - #region Parse & Cache - - private static Dictionary _cache = new Dictionary(); - - /// 解析索引特性,默认使用缓存以提升性能。 - public static IndexAttribute[] Parse(bool useCache = true) where T : class, new() => Parse(typeof(T), useCache); - - /// 解析索引特性,默认使用缓存以提升性能。 - public static IndexAttribute[] Parse(Type type, bool useCache = true) - { - if (type == null) return new IndexAttribute[0]; - var cacheKey = type.FullName; - - if (useCache) - { - lock (_cache) - { - IndexAttribute[] cached; - if (_cache.TryGetValue(cacheKey, out cached)) return cached; - } - } - - var attributes = type.GetCustomAttributes(typeof(IndexAttribute), false); - var list = new List(); - foreach (var attribute in attributes) - { - var ia = attribute as IndexAttribute; - if (!ia) continue; - list.Add(ia); - } - - var items = list.ToArray(); - if (useCache) - { - lock (_cache) - { - if (_cache.ContainsKey(cacheKey)) _cache[cacheKey] = items; - else _cache.Add(cacheKey, items); - } - } - - return items; - } - - #endregion - - } - -} diff --git a/Apewer/Source/KeyRecord.cs b/Apewer/Source/KeyRecord.cs new file mode 100644 index 0000000..e1b12d1 --- /dev/null +++ b/Apewer/Source/KeyRecord.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Source +{ + + /// 数据库记录通用字段模型,此模型中的 Key 属性带有主键特性。 + /// 带有 Independent 特性的模型不包含此类型声明的属性。 + [Serializable] + public abstract class KeyRecord : Record + { + + /// 记录主键,一般使用 GUID 的字符串形式。 + /// + /// 注: + /// 1. 默认长度为 32,需要修改长度时应该重写此属性; + /// 2. 带有 Independent 特性的模型不包含此属性。 + /// + [PrimaryKey] + [Column("_key", ColumnType.NVarChar, 32)] + public override string Key { get => base.Key; set => base.Key = value; } + + } + +} diff --git a/Apewer/Source/ModelException.cs b/Apewer/Source/ModelException.cs new file mode 100644 index 0000000..c7a57d4 --- /dev/null +++ b/Apewer/Source/ModelException.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Source +{ + + /// 表示在解析数据模型过程中发生的错误。 + [Serializable] + public class ModelException : Exception + { + + const string EmptyMessage = "(无消息)"; + + string _msg = null; + Type _model = null; + + /// 获取描述当前异常的消息。 + public override string Message { get => _msg; } + + /// 数据模型的类型。 + public Type Model { get => _model; } + + /// 初始化 类的新实例。 + /// 描述当前异常的消息。 + /// 数据模型的类型。 + public ModelException(string message, Type model = null) + { + _msg = string.IsNullOrEmpty(message) ? EmptyMessage : message; + _model = model; + } + + /// 表示数据模型类型无效的异常实例。 + public static ArgumentNullException InvalidType() => new ArgumentNullException("数据模型的类型无效。"); + + /// 表示数据模型结构无效的异常实例。 + /// 数据模型。 + public static ModelException InvalidStructure(Type model) + { + if (model == null) return new ModelException("数据模型的类型无效。"); + return new ModelException($"类型 <{model.Name}> 的数据模型结构无效。"); + } + + /// 表示表名称无效的异常实例。 + /// 数据模型。 + public static ModelException InvalidTableName(Type model) + { + if (model == null) return new ModelException("数据模型的类型无效。"); + return new ModelException($"类型 <{model.Name}> 不包含表名称。"); + } + + /// 表示丢失了 Key 字段的异常实例。 + /// 数据模型。 + public static ModelException MissingKey(Type model) + { + if (model == null) return new ModelException("数据模型的类型无效。"); + return new ModelException($"类型 <{model.Name}> 不包含 Key 属性的字段。"); + } + + /// 表示丢失了 Flag 字段的异常实例。 + /// 数据模型。 + public static ModelException MissingFlag(Type model) + { + if (model == null) return new ModelException("数据模型的类型无效。"); + return new ModelException($"类型 <{model.Name}> 不包含 Flag 属性的字段。"); + } + + /// 表示表名称无效的异常实例。 + public static ModelException InvalidTableName() => InvalidTableName(typeof(TModel)); + + /// 表示丢失了 Key 字段的异常实例。 + public static ModelException MissingKey() => MissingKey(typeof(TModel)); + + /// 表示丢失了 Flag 字段的异常实例。 + public static ModelException MissingFlag() => MissingFlag(typeof(TModel)); + + } + + /// 表示在解析数据模型过程中发生的错误。 + [Serializable] + public class ModelException : ModelException + { + + /// 初始化 类的新实例。 + /// 描述当前异常的消息。 + public ModelException(string message) : base(message, typeof(TModel)) { } + + /// 表示表名称无效的异常实例。 + public static ModelException InvalidTableName() => InvalidTableName(typeof(TModel)); + + /// 表示丢失了 Key 字段的异常实例。 + public static ModelException MissingKey() => MissingKey(typeof(TModel)); + + /// 表示丢失了 Flag 字段的异常实例。 + public static ModelException MissingFlag() => MissingFlag(typeof(TModel)); + + } + +} diff --git a/Apewer/Source/SourceUtility.cs b/Apewer/Source/SourceUtility.cs index e2d36ec..adc3816 100644 --- a/Apewer/Source/SourceUtility.cs +++ b/Apewer/Source/SourceUtility.cs @@ -13,57 +13,19 @@ namespace Apewer.Source public static class SourceUtility { - #region As - - /// 转换模型类型。 - public static TDst[] As(this TSrc[] input) where TDst : class - { - if (input == null) return null; - var count = input.Length; - var output = new TDst[count]; - for (var i = 0; i < count; i++) - { - var item = input[i]; - if (item == null) continue; - output[i] = item as TDst; // 此处可能抛出异常。 - } - return output; - } - - /// 转换模型类型。 - public static Result As(this Result input) where TDst : class - { - if (input == null) return null; - if (!input.HasValue) return new Result(input.Code, input.Message); - var value = input.Value as TDst; - if (value == null) - { - var src = input.Value.GetType().FullName; - var dst = typeof(TDst).FullName; - return new Result($"无法将记录从转换 {src} 到 {dst}。"); - } - return new Result(value); - } - - /// 转换模型类型。 - public static Result As(this Result input) where TDst : class - { - if (input == null) return null; - if (!input.HasValue) return new Result(input.Code, input.Message); - var count = input.Value.Length; - var output = new TDst[count]; - for (var i = 0; i < count; i++) output[i] = input.Value[i] as TDst; - return new Result(output); - } - - #endregion - #region IQuery -> IRecord /// 读取所有行,生成列表。 - public static T[] Fill(this IQuery query) where T : class, new() => As(Fill(query, typeof(T))); + 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]; @@ -203,172 +165,6 @@ namespace Apewer.Source #endregion - #region IOrm - - /// 查询记录。 - /// 数据库对象。 - /// 记录模型。 - /// SQL 语句。 - /// 为 SQL 命令提供参数。 - public static Result Query(IDbAdo database, Type model, string sql, IEnumerable parameters) - { - if (database == null) return new Result("数据库无效。"); - if (model == null) return new Result("模型类型无效。"); - if (string.IsNullOrEmpty(sql)) return new Result("SQL 语句无效。"); - using (var query = database.Query(sql, parameters)) - { - if (query == null) return new Result("查询实例无效。"); - if (query.Table == null) - { - if (!string.IsNullOrEmpty(query.Message)) return new Result(query.Message); - return new Result("查询实例不包含数据表。"); - } - try - { - var array = Fill(query, model); - return new Result(array); - } - catch (Exception ex) - { - return new Result(ex); - } - } - } - - // /// 查询记录。 - // /// 记录模型。 - // /// 数据库对象。 - // /// SQL 语句。 - // public static Result Query(IDbClientAdo database, string sql) where T : class, new() => As(Query(database, typeof(T), sql)); - - /// 查询记录。 - /// 数据库对象。 - /// 记录模型。 - /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result Query(IDbAdo database, Type model, Func sqlGetter) - { - if (sqlGetter == null) return new Result("SQL 语句获取函数无效。"); - try - { - var tableName = TableStructure.Parse(model).TableName; - if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。"); - return Query(database, model, sqlGetter(tableName), null); - } - catch (Exception ex) - { - return new Result(ex); - } - } - - /// 查询记录。 - /// 记录模型。 - /// 数据库对象。 - /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result Query(IDbAdo database, Func sqlGetter) where T : class, new() => As(Query(database, typeof(T), sqlGetter)); - - /// 获取具有指定主键的记录。 - /// 数据库对象。 - /// 记录模型。 - /// 主键。 - /// 生成 SQL 语句的函数,传入参数为表名和主键值。 - public static Result Get(IDbAdo database, Type model, string key, Func sqlGetter) - { - if (sqlGetter == null) return new Result("SQL 语句获取函数无效。"); - - var safetyKey = TextUtility.SafeKey(key); - if (string.IsNullOrEmpty(safetyKey)) return new Result("主键无效。"); - - var query = null as IQuery; - var record = null as object; - try - { - record = Activator.CreateInstance(model); - var ts = TableStructure.Parse(model); - var tableName = ts.TableName; - if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。"); - var sql = sqlGetter(tableName, safetyKey); - - query = database.Query(sql); - if (query.Table == null) return new Result("没有获取到记录。"); - record = Row(query, 0, model, ts); - } - catch (Exception ex) - { - RuntimeUtility.Dispose(query); - return new Result(ex); - } - RuntimeUtility.Dispose(query); - if (record == null) return new Result("没有获取到记录。"); - return new Result(record); - } - - /// 获取具有指定主键的记录。 - /// 记录模型。 - /// 数据库对象。 - /// 主键。 - /// 生成 SQL 语句的函数,传入参数为表名和主键值。 - public static Result Get(IDbAdo database, string key, Func sqlGetter) where T : class, IRecord, new() => As(Get(database, typeof(T), key, sqlGetter)); - - /// 获取主键。 - /// 数据库对象。 - /// 记录模型。 - /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result Keys(IDbAdo database, Type model, Func sqlGetter) - { - if (database == null) return new Result("数据库无效。"); - if (model == null) return new Result("模型类型无效。"); - if (sqlGetter == null) return new Result("SQL 语句获取函数无效。"); - - var tableStructure = null as TableStructure; - try - { - tableStructure = TableStructure.Parse(model); - } - catch (Exception ex) - { - return new Result(ex); - } - - var tableName = tableStructure.TableName; - if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。"); - - var sql = sqlGetter(tableName); - var query = null as IQuery; - try - { - query = database.Query(sql); - if (query == null) return new Result("查询实例无效。"); - - var list = new List(query.Rows); - for (var r = 0; r < query.Rows; r++) - { - var key = TextUtility.SafeKey(query.Text(r)); - if (string.IsNullOrEmpty(key)) continue; - list.Add(key); - } - query.Dispose(); - list.Capacity = list.Count; - var array = list.ToArray(); - return new Result(array); - } - catch (Exception ex) - { - RuntimeUtility.Dispose(query); - return new Result(ex); - } - } - - /// 获取主键。 - /// 记录模型。 - /// 数据库对象。 - /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result Keys(IDbAdo database, Func sqlGetter) where T : IRecord - { - return Keys(database, typeof(T), sqlGetter); - } - - #endregion - #region Record /// 修复记录属性。 @@ -449,72 +245,211 @@ namespace Apewer.Source #endregion - #region DbClient + #region Query /// 简单查询:取结果中第 0 列所有单元格的文本形式,可指定查询后关闭服务器连接,返回结果中不包含无效文本。 /// 数据库客户端。 /// 用于查询的 SQL 语句。 - /// 查询后,关闭数据库链接。 - public static string[] SimpleColumn(this IDbAdo source, string sql, bool close = false) + /// + public static string[] Column(this IDbAdo source, string sql) { if (source == null) return new string[0]; - var ab = new ArrayBuilder(); + + var pool = null as string[]; + var rows = 0; + var count = 0; using (var query = source.Query(sql)) { - var rows = query.Rows; - if (rows > 0) + 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 added = 0; - for (int i = 0; i < rows; i++) - { - var cell = TextUtility.Trim(query.Text(i)); - if (string.IsNullOrEmpty(cell)) continue; - ab.Add(cell); - added++; - } + var cell = TextUtility.Trim(query.Text(i)); + if (string.IsNullOrEmpty(cell)) continue; + pool[count] = cell; + count++; } } - if (close) RuntimeUtility.Dispose(source); - return ab.Export(); + + if (count < 1) return new string[0]; + + var array = new string[count]; + Array.Copy(pool, 0, array, 0, count); + return array; } /// 简单查询:取结果中第 0 行、第 0 列单元格中的文本,可指定查询后关闭服务器连接。 - /// 数据库客户端。 + /// 数据库客户端。 /// 用于查询的 SQL 语句。 - /// 查询后,关闭数据库链接。 - public static string SimpleCell(this IDbAdo source, string sql, bool close = false) + /// + /// + public static string Cell(this IDbAdo dbClient, string sql) { - if (source == null) return null; - var value = null as string; - using (var query = source.Query(sql)) value = TextUtility.Trim(query.Text()); - if (close) RuntimeUtility.Dispose(source); - return value; + 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()); + return value; + } } /// 查询。 + /// 数据库连接。 + /// SQL 语句。 + /// SQL 参数。 /// public static IQuery Query(this IDbClient dbClient, string sql, IEnumerable> parameters) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); - return dbClient.Query(sql, Parameters(dbClient, parameters)); + 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 IDbClient 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); + } + } + + #endregion + + #region Execute + /// 执行 SQL 语句,并加入参数。 /// public static IExecute Execute(this IDbClient dbClient, string sql, IEnumerable> parameters, bool autoTransaction = false) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); - return dbClient.Execute(sql, Parameters(dbClient, parameters), autoTransaction); + 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 IDbClient 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); + } } + #endregion + + #region Parameter + /// - static List Parameters(IDbClient dbClient, IEnumerable> parameters) + static List ParametersByProperites(IDbClient 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.EndsWith("@")) 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(); - if (parameters != null) + foreach (var kvp in dict) + { + var p = dbClient.Parameter(kvp.Key, kvp.Value); + ps.Add(p); + } + return ps; + } + + /// + static List Parameters(IDbClient 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) { - foreach (var parameter in parameters) ps.Add(dbClient.Parameter(parameter.Key, parameter.Value)); + var name = kvp.Key; + if (name.IsEmpty()) continue; + + // 属性不可重复。 + if (!name.EndsWith("@")) 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; } @@ -625,12 +560,13 @@ namespace Apewer.Source if (sc < 1) return array; // 解析模型列。 + var cas = ts.Fillable; var dc = 0; - var dfs = new string[ts.Columns.Length]; - var dts = new ColumnAttribute[ts.Columns.Length]; - for (var i = 0; i < ts.Columns.Length; i++) + var dfs = new string[cas.Length]; + var dts = new ColumnAttribute[cas.Length]; + for (var i = 0; i < cas.Length; i++) { - var ca = ts.Columns[i]; + var ca = cas[i]; var key = ca.Field.Lower(); if (string.IsNullOrEmpty(key)) continue; if (dfs.Contains(key)) continue; diff --git a/Apewer/Source/SqlException.cs b/Apewer/Source/SqlException.cs new file mode 100644 index 0000000..d35d8f0 --- /dev/null +++ b/Apewer/Source/SqlException.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Source +{ + + /// 表示在执行 SQL 语句执行过程中发生的错误。 + [Serializable] + public sealed class SqlException : Exception + { + + const string EmptyMessage = "(无消息)"; + + string _msg = null; + string _sql = null; + + /// 获取描述当前异常的消息。 + public override string Message { get => _msg; } + + /// 获取引发异常的 SQL 语句。 + public string Statement { get => _sql; } + + /// 初始化 类的新实例。 + /// 描述当前异常的消息。 + /// 附带 SQL 语句。 + public SqlException(string message, string statement = null) + { + _msg = string.IsNullOrEmpty(message) ? EmptyMessage : message; + + _sql = statement; + } + + /// 初始化 类的新实例。 + /// 用于获取消息的查询结果。 + /// 附带 SQL 语句。 + public SqlException(IQuery query, string statement = null) + { + if (query == null) + { + _msg = "查询结果实例无效。"; + _sql = statement; + return; + } + + _msg = query.Message; + if (string.IsNullOrEmpty(_msg)) _msg = EmptyMessage; + + _sql = statement; + } + + /// 初始化 类的新实例。 + /// 用于获取消息的执行结果。 + /// 附带 SQL 语句。 + public SqlException(IExecute execute, string statement = null) + { + if (execute == null) + { + _msg = "执行结果实例无效。"; + return; + } + + _msg = execute.Message; + if (string.IsNullOrEmpty(_msg)) _msg = EmptyMessage; + + _sql = statement; + } + + } + +} diff --git a/Apewer/Source/TableAttribute.cs b/Apewer/Source/TableAttribute.cs index 35633e0..2fd55b1 100644 --- a/Apewer/Source/TableAttribute.cs +++ b/Apewer/Source/TableAttribute.cs @@ -21,6 +21,7 @@ namespace Apewer.Source private string _name = null; private string _store = null; private Type _model = null; + private bool _primarykey = false; /// 标记表属性。 public TableAttribute(string name = null, string store = null) @@ -38,6 +39,9 @@ namespace Apewer.Source /// 使用此特性的类型。 public Type Model { get => _model; } + /// 模型实现了 接口。 + public bool PrimaryKey { get => _primarykey; } + /// 到 Boolean 的隐式转换,判断 有效。 public static implicit operator bool(TableAttribute instance) { @@ -64,6 +68,7 @@ namespace Apewer.Source #region cache private static Dictionary _tac = new Dictionary(); + private static Type InterfacePrimaryKey = typeof(IRecordPrimaryKey); /// 解析表特性,默认使用缓存以提升性能。 public static TableAttribute Parse(bool useCache = true) where T : class, new() => Parse(typeof(T), useCache); @@ -103,6 +108,7 @@ namespace Apewer.Source ta._model = type; if (string.IsNullOrEmpty(ta.Name)) ta._name = type.Name; ta.Independent = RuntimeUtility.Contains(type, true); + ta._primarykey = RuntimeUtility.IsInherits(type, InterfacePrimaryKey); if (useCache) { diff --git a/Apewer/Source/TableStructure.cs b/Apewer/Source/TableStructure.cs index fc732d3..b97e576 100644 --- a/Apewer/Source/TableStructure.cs +++ b/Apewer/Source/TableStructure.cs @@ -21,7 +21,7 @@ namespace Apewer.Source ColumnAttribute _key = null; ColumnAttribute _flag = null; ColumnAttribute[] _columns = null; - IndexAttribute[] _indexes = null; + ColumnAttribute[] _fillable = null; private TableStructure() { } @@ -31,12 +31,12 @@ namespace Apewer.Source /// 表特性。 public TableAttribute Table { get => _table; } - /// 索引。 - public IndexAttribute[] Indexed { get => _indexes; } - /// 列信息。 public ColumnAttribute[] Columns { get => _columns; } + /// 可填充的列信息。 + public ColumnAttribute[] Fillable { get => _fillable; } + /// 主键。 public ColumnAttribute Key { get => _key; } @@ -87,6 +87,7 @@ namespace Apewer.Source public static TableStructure Parse(bool useCache = true, bool force = false) where T : IRecord => Parse(typeof(T), useCache, force); /// 解析表结构。 + /// 表结构。类型不可用于表结构时返回 NULL 值。 public static TableStructure Parse(Type model, bool useCache = true, bool force = false) { var type = model; @@ -94,6 +95,7 @@ namespace Apewer.Source // 使用缓存。 var cacheKey = type.FullName; + if (force) cacheKey = "[force] " + cacheKey; if (useCache) { lock (_tsc) @@ -103,36 +105,63 @@ namespace Apewer.Source } } - // 获取 Table Attribute。 + // 解析 TableAttribute。 var ta = TableAttribute.Parse(type, useCache, force); + if (!ta && !force) return null; - // 获取索引。 - var ias = IndexAttribute.Parse(type); - - // 遍历所有属性。 + // 类型。 + var isRecord = RuntimeUtility.IsInherits(type, typeof(Record)); var properties = type.GetProperties(); + var total = properties.Length; + + // 解析 ColumnAttribute。 var key = null as ColumnAttribute; var flag = null as ColumnAttribute; - var columns = new ColumnAttribute[properties.Length]; + var columns = new ColumnAttribute[total]; var columnsCount = 0; - if (properties.Length > 0) + var fillable = new List(total); + if (total > 0) { - var addedFields = new List(properties.Length); + var caForce = force || (ta ? ta.AllProperties : false); + var addedFields = new List(total); foreach (var property in properties) { - // 解析 ColumnAttribute,抛弃无效。 - var ca = ColumnAttribute.Parse(type, property, ta); - if (ca == null) continue; - - // 检查 field 重复,只保留第一个。 - var field = ca.Field; - if (addedFields.Contains(field)) continue; - addedFields.Add(field); - - if (property.Name == "Key") key = ca; - if (property.Name == "Flag") flag = ca; - columns[columnsCount] = ca; - columnsCount += 1; + var ca = ColumnAttribute.Parse(property, caForce); + if (ca != null) + { + // 检查 field 重复,只保留第一个。 + var field = ca.Field; + if (!addedFields.Contains(field)) + { + addedFields.Add(field); + columns[columnsCount] = ca; + columnsCount += 1; + + if (isRecord) + { + if (property.Name == "Key") + { + key = ca; + if (ta != null && ta.PrimaryKey) ca.SetPrimaryKey(); + } + else if (property.Name == "Flag") + { + flag = ca; + } + } + } + + // 可查询的列。 + fillable.Add(ca); + continue; + } + + // 可查询的列。 + if (!caForce) + { + ca = ColumnAttribute.Parse(property, caForce); + if (ca) fillable.Add(ca); + } } } if (columnsCount > 0 && columnsCount != columns.Length) columns = columns.Slice(0, columnsCount); @@ -143,11 +172,11 @@ namespace Apewer.Source // 返回结果。 var ts = new TableStructure(); ts._table = ta; - ts._indexes = ias; ts._key = key; ts._flag = flag; ts._columns = columns; ts._model = model; + ts._fillable = fillable.ToArray(); // 加入缓存。 if (useCache) diff --git a/Apewer/StorageUtility.cs b/Apewer/StorageUtility.cs index f52150e..9253b3c 100644 --- a/Apewer/StorageUtility.cs +++ b/Apewer/StorageUtility.cs @@ -106,6 +106,8 @@ namespace Apewer /// 无效的路径字符。 public static char[] InvalidPathChars { + // SMB 共享文件夹无效字符 + // ! " # % & ' ( ) * + , / : ; < = > ? @ [ ] \ ^ ` { } | ~ get => new char[] { '\\', '/', '\'', '"', ':', '*', '?', '<', '>', '|', '\0', '\a', '\b', '\t', '\n', '\v', '\f', '\r', diff --git a/Apewer/TextUtility.cs b/Apewer/TextUtility.cs index 1a74d0a..2d3903a 100644 --- a/Apewer/TextUtility.cs +++ b/Apewer/TextUtility.cs @@ -111,10 +111,10 @@ namespace Apewer } /// 合并为字符串。 - public static string Merge(params object[] cells) => PrivateJoin(null, CollectionHelper.ParseParams(cells)); + public static string Merge(params object[] cells) => PrivateJoin(null, CollectionUtility.ParseParams(cells)); /// 合并为字符串。 - public static string Join(string separator, params object[] cells) => PrivateJoin(separator, CollectionHelper.ParseParams(cells)); + public static string Join(string separator, params object[] cells) => PrivateJoin(separator, CollectionUtility.ParseParams(cells)); /// 重复指定字符,直到达到指定长度。 /// 要重复的字符。 @@ -1059,6 +1059,43 @@ namespace Apewer return true; } + /// 解析编码名称。 + /// 解析失败时,返回 NULL 值。 + private static Encoding ParseEncoding(string encoding) + { + if (encoding.IsEmpty()) return null; + + var lower = encoding.Lower(); + var nick = lower.Replace("-", ""); + switch (nick) + { + case "ascii": + return Encoding.ASCII; + case "bigendia": + case "bigendianunicode": + return Encoding.BigEndianUnicode; + case "utf7": + return Encoding.UTF7; + case "utf8": + return Encoding.UTF8; + case "utf16": + case "unicode": + return Encoding.Unicode; + case "utf32": + return Encoding.UTF7; + + case "default": + return Encoding.Default; + + case "ansi": + case "gb2312": + case "gb18030": + return Encoding.Default; + } + + return null; + } + #endregion } diff --git a/Apewer/Web/ApiModel.cs b/Apewer/Web/ApiModel.cs index 9ed9d4b..40b7813 100644 --- a/Apewer/Web/ApiModel.cs +++ b/Apewer/Web/ApiModel.cs @@ -107,6 +107,7 @@ namespace Apewer.Web } _provider.ResponseBody().Write(stream); _provider.Sent(); + if (dispose) RuntimeUtility.Dispose(stream); } #endregion @@ -228,18 +229,19 @@ namespace Apewer.Web var info = new FileInfo(Path); if (string.IsNullOrEmpty(Attachment)) Attachment = info.Name; - var stream = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read); - Output(stream, true); + using (var stream = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + Output(stream, false); + } } catch { } } /// /// - public ApiFileModel(string path, string name = null) + public ApiFileModel(string path) { SetPath(path); - Attachment = name; } } diff --git a/Apewer/Web/ApiProcessor.cs b/Apewer/Web/ApiProcessor.cs index d5d65a3..fcb9991 100644 --- a/Apewer/Web/ApiProcessor.cs +++ b/Apewer/Web/ApiProcessor.cs @@ -214,7 +214,11 @@ namespace Apewer.Web if (function != null) { + // 调用 API,获取返回值。 var result = function.Method.Invoke(controller, ReadParameters(request, function)); + if (response.StopReturn) return; + + // 检查返回值。 if (result == null || function.Returnable == null) return; var returnable = function.Returnable; diff --git a/Apewer/Web/ApiResponse.cs b/Apewer/Web/ApiResponse.cs index fc953f8..9472be5 100644 --- a/Apewer/Web/ApiResponse.cs +++ b/Apewer/Web/ApiResponse.cs @@ -16,6 +16,8 @@ namespace Apewer.Web private ApiModel _model = null; private Json _data = Json.NewObject(); + internal bool StopReturn = false; + /// API 的执行时间,以毫秒为单位。 public long Duration { get; set; } diff --git a/Apewer/Web/ApiUtility.cs b/Apewer/Web/ApiUtility.cs index 4fdb523..7f2fe30 100644 --- a/Apewer/Web/ApiUtility.cs +++ b/Apewer/Web/ApiUtility.cs @@ -756,6 +756,13 @@ namespace Apewer.Web return json; } + /// 停止 Invoker 对返回值的处理。 + public static void StopReturn(ApiResponse response) + { + if (response == null) return; + response.StopReturn = true; + } + #endregion #region ApiModel diff --git a/Apewer/_Delegates.cs b/Apewer/_Delegates.cs index a214c2b..a47c4fa 100644 --- a/Apewer/_Delegates.cs +++ b/Apewer/_Delegates.cs @@ -5,22 +5,6 @@ using System.IO; namespace Apewer { - // /// 封装一个方法,该方法不具有参数且不返回值。 - // public delegate void Action(); - - // /// 表示当事件提供数据时将处理该事件的方法。 - // /// 事件生成的事件数据的类型。 - // /// 事件源。 - // /// 包含事件数据的对象。 - // public delegate void EventHandler(object sender, TEventArgs e); - - // /// 封装一个方法,该方法具有一个参数,且返回由 TResult 参数指定的类型的值。 - // /// 此委托封装的方法的参数类型。 - // /// 此委托封装的方法的返回值类型。 - // /// 此委托封装的方法的参数。 - // /// 此委托封装的方法的返回值。 - // public delegate TResult Func(T arg); - /// public delegate void Event(object sender); @@ -65,59 +49,4 @@ namespace Apewer /// 日志文件距离今天的天数,例:昨日为 1。 public delegate void LogCollector(string path, int days); -#if NET20 - - /// 封装一个方法,该方法不具有参数,且返回由 TResult 参数指定的类型的值。 - /// 此委托封装的方法的返回值类型。 - /// 此委托封装的方法的返回值。 - public delegate TResult Func(); - - /// 封装一个方法,该方法具有一个参数,且返回由 TResult 参数指定的类型的值。 - /// 此委托封装的方法的参数类型。 - /// 此委托封装的方法的返回值类型。 - /// 此委托封装的方法的参数。 - /// 此委托封装的方法的返回值。 - public delegate TResult Func(T arg); - - /// 封装一个方法,该方法具有两个参数,并返回由 TResult 参数指定的类型的值。 - /// 此委托封装的方法的第一个参数的类型。 - /// 此委托封装的方法的第二个参数的类型。 - /// 此委托封装的方法的返回值类型。 - /// 此委托封装的方法的第一个参数。 - /// 此委托封装的方法的第二个参数。 - /// 此委托封装的方法的返回值。 - public delegate TResult Func(T1 arg1, T2 arg2); - - /// 封装一个方法,该方法不具有参数且不返回值。 - public delegate void Action(); - - /// 封装一个方法,该方法不具有参数且不返回值。 - public delegate void Action(); - - /// 封装一个方法,该方法不具有参数且不返回值。 - public delegate void Action(); - - /// 封装一个方法,该方法不具有参数且不返回值。 - public delegate void Action(); - - /// 封装一个方法,该方法不具有参数且不返回值。 - public delegate void Action(); - - /// 封装一个方法,该方法不具有参数且不返回值。 - public delegate void Action(); - - /// 封装一个方法,该方法不具有参数且不返回值。 - public delegate void Action(); - - /// 封装一个方法,该方法不具有参数且不返回值。 - public delegate void Action(); - - /// 表示当事件提供数据时将处理该事件的方法。 - /// 事件生成的事件数据的类型。 - /// 事件源。 - /// 包含事件数据的对象。 - public delegate void EventHandler(object sender, TEventArgs e); - -#endif - } diff --git a/Apewer/_Extensions.cs b/Apewer/_Extensions.cs index 9b9fd96..2f006d7 100644 --- a/Apewer/_Extensions.cs +++ b/Apewer/_Extensions.cs @@ -5,12 +5,12 @@ using Apewer.Web; using System; using System.Collections; using System.Collections.Generic; -using System.Collections.Specialized; using System.Data; using System.Diagnostics; using System.IO; using System.Reflection; using System.Text; +using System.Collections.Specialized; #if !NET20 using System.Dynamic; @@ -66,7 +66,7 @@ public static class Extensions /// 要设置属性的对象。 /// 属性值。 /// - public static void SetValue(this PropertyInfo property , object obj, object value) + public static void SetValue(this PropertyInfo property, object obj, object value) { if (property == null) throw new ArgumentNullException(nameof(property)); property.SetValue(obj, value, null); @@ -278,7 +278,7 @@ public static class Extensions /// 从毫秒时间戳获取 DateTime 对象。发生异常且不允许异常时将返回 1970-01-01 00:00:00.000。 /// - public static DateTime DateTime(this long stamp, bool throwException = true) => ClockUtility.FromStamp(stamp, throwException); + public static DateTime DateTime(this long stamp, DateTimeKind kind = DateTimeKind.Unspecified, bool throwException = true) => ClockUtility.FromStamp(stamp, kind, throwException); #endregion @@ -437,55 +437,55 @@ public static class Extensions } /// 添加元素。 - public static bool Add(this IList> @this, TKey key, TValue value) => CollectionHelper.Add(@this, key, value); + public static bool Add(this IList> @this, TKey key, TValue value) => CollectionUtility.Add(@this, key, value); /// 判断集合为空。 - public static bool IsEmpty(this IEnumerable @this) => CollectionHelper.IsEmpty(@this); + public static bool IsEmpty(this IEnumerable @this) => CollectionUtility.IsEmpty(@this); /// 判断集合存在元素。 - public static bool NotEmpty(this IEnumerable @this) => CollectionHelper.NotEmpty(@this); + public static bool NotEmpty(this IEnumerable @this) => CollectionUtility.NotEmpty(@this); /// 检查集合是否包含 item。 - public static bool Contains(this IEnumerable @this, T item) => CollectionHelper.Contains(@this, item); + public static bool Contains(this IEnumerable @this, T item) => CollectionUtility.Contains(@this, item); /// 获取 item 在集合中的偏移位置,不存在时返回 -1。 - public static int IndexOf(this IEnumerable objects, T item) => CollectionHelper.IndexOf(objects, item); + public static int IndexOf(this IEnumerable objects, T item) => CollectionUtility.IndexOf(objects, item); /// 获取集合中元素的数量。 - public static int Count(this IEnumerable @this) => CollectionHelper.Count(@this); + public static int Count(this IEnumerable @this) => CollectionUtility.Count(@this); /// 对元素去重,且去除 NULL 值。 - public static T[] Distinct(this IEnumerable @this) => CollectionHelper.Distinct(@this); + public static T[] Distinct(this IEnumerable @this) => CollectionUtility.Distinct(@this); /// 获取可枚举集合的部分元素。 - public static T[] Slice(this IEnumerable @this, int start = 0, int count = -1, Func stuffer = null) => CollectionHelper.Slice(@this, start, count, stuffer); + public static T[] Slice(this IEnumerable @this, int start = 0, int count = -1, Func stuffer = null) => CollectionUtility.Slice(@this, start, count, stuffer); /// 安全转换为 List<> 对象。可指定排除 NULL 值元素。 - public static List List(this IEnumerable @this, bool excludeNull = false) => CollectionHelper.ToList(@this, excludeNull); + public static List List(this IEnumerable @this, bool excludeNull = false) => CollectionUtility.List(@this, excludeNull); /// 安全转换为 <>[] 对象。可指定排除 NULL 值元素。 - public static T[] Array(IEnumerable @this, bool excludeNull = false) => CollectionHelper.ToArray(@this, excludeNull); + public static T[] Array(IEnumerable @this, bool excludeNull = false) => CollectionUtility.Array(@this, excludeNull); /// 对列表中的元素排序。 - public static List Sort(this List @this, Func comparison) => CollectionHelper.Sort(@this, comparison); + public static List Sort(this List @this, Func comparison) => CollectionUtility.Sort(@this, comparison); /// 对字典中的键排序。 - public static Dictionary SortKey(this Dictionary @this, Func comparison) => CollectionHelper.SortKey(@this, comparison); + public static Dictionary SortKey(this Dictionary @this, Func comparison) => CollectionUtility.SortKey(@this, comparison); /// 对字典中的键排序。 - public static Dictionary SortKey(this Dictionary @this) where TKey : IComparable => CollectionHelper.SortKey(@this, (a, b) => a.CompareTo(b)); + public static Dictionary SortKey(this Dictionary @this) where TKey : IComparable => CollectionUtility.SortKey(@this, (a, b) => a.CompareTo(b)); /// 对字典中的值排序。 - public static Dictionary SortValue(this Dictionary @this, Func comparison) => CollectionHelper.SortValue(@this, comparison); + public static Dictionary SortValue(this Dictionary @this, Func comparison) => CollectionUtility.SortValue(@this, comparison); /// 对字典中的值排序。 - public static Dictionary SortValue(this Dictionary @this) where TValue : IComparable => CollectionHelper.SortValue(@this, (a, b) => a.CompareTo(b)); + public static Dictionary SortValue(this Dictionary @this) where TValue : IComparable => CollectionUtility.SortValue(@this, (a, b) => a.CompareTo(b)); /// 获取集合中的第一个元素。可指定失败时的默认返回值。 - public static T First(this IEnumerable collection, T failed = default(T)) => CollectionHelper.First(collection, failed); + public static T First(this IEnumerable collection, T failed = default(T)) => CollectionUtility.First(collection, failed); /// 获取集合中的最后一个元素。可指定失败时的默认返回值。 - public static T Last(this IEnumerable collection, T failed = default(T)) => CollectionHelper.Last(collection, failed); + public static T Last(this IEnumerable collection, T failed = default(T)) => CollectionUtility.Last(collection, failed); /// 生成 StringPairs 对象实例为副本。 public static StringPairs StringPairs(this NameValueCollection @this) => Apewer.StringPairs.From(@this); @@ -604,20 +604,11 @@ public static class Extensions public static void Json(this ApiResponse @this, Json json, bool indented = true, bool camel = false) => ApiUtility.Model(@this, new ApiJsonModel(json, camel, indented)); /// 输出文件。 - public static void File(this ApiResponse @this, string path, string name = null) => ApiUtility.Model(@this, new ApiFileModel(path, name)); + public static void File(this ApiResponse @this, string path) => ApiUtility.Model(@this, new ApiFileModel(path)); /// 重定向。 public static void Redirect(this ApiResponse @this, string location) => ApiUtility.Model(@this, new ApiRedirectModel() { Location = location }); - /// 设置响应,当发生错误时设置响应。返回错误信息。 - public static string Set(this ApiResponse @this, IList list, bool lower = true, int depth = -1, bool force = false) => ApiUtility.Respond(@this, list, lower, depth, force); - - /// 设置响应,当发生错误时设置响应。返回错误信息。 - public static string Set(this ApiResponse @this, IRecord record, bool lower = true) => ApiUtility.Respond(@this, record, lower); - - /// 设置响应,当发生错误时设置响应。返回错误信息。 - public static string Set(this ApiResponse @this, Json data, bool lower = true) => ApiUtility.Respond(@this, data, lower); - #endregion } diff --git a/ChangeLog.md b/ChangeLog.md index e063177..b3a7022 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,21 @@  ### 最新提交 +### 6.7.0 +- 大更新 + - Result 模型已完全删除,使用 Result 的程序现在已经改为抛出异常,解决程序返回值混乱的问题; + - 新增了 SqlException 和 ModelException 类型。 +- 新功能 + - 新增 ApiUtility.StopReturn 方法,可阻止框架解析 ApiResponse 的返回值; + - 新增 RuntimeUtility.IsAnonymousType 方法,用于判断匿名对象; + - 新增 NetworkUtility.Ping 方法,替代原 Icmp 类。 + - 增加了基类 KeyRecord 和接口 IRecordPrimaryKey,可以方便地将 Key 属性标记为主键; + - 新增 CollectionUtility 类;开放其中方法,不再限于扩展方法; + - 新增关于时区的方法。 +- 问题修正 + - 修正 HttpClient 缺少 ResponseHeaders 的问题; + - 修正 ApiModel 不释放 Stream 的问题。 + ### 6.6.28 - BytesUtility:优化 ToX2 性能; - Logger:修正默认删除所有日志文件的问题;