diff --git a/Apewer.Source/Source/Access.cs b/Apewer.Source/Source/Access.cs index b5bd605..8857cd5 100644 --- a/Apewer.Source/Source/Access.cs +++ b/Apewer.Source/Source/Access.cs @@ -24,7 +24,7 @@ namespace Apewer.Source #if NETFRAMEWORK - public partial class Access : IDbClientBase, IDbClientAdo, IDisposable + public partial class Access : IDbAdo { #region 连接 diff --git a/Apewer.Source/Source/DbClient.cs b/Apewer.Source/Source/DbClient.cs new file mode 100644 index 0000000..a0541db --- /dev/null +++ b/Apewer.Source/Source/DbClient.cs @@ -0,0 +1,480 @@ +#if DEBUG + +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Text; + +namespace Apewer.Source +{ + + /// 数据库客户端基类。 + + + public abstract class DbClient : IDbClient + { + + /// + public virtual Logger Logger { get; set; } + + /// + public DbClient(Timeout timeout) { _timeout = timeout ?? Timeout.Default; } + + #region connection + + Timeout _timeout = null; + IDbConnection _conn = null; + string _str = null; + + /// + public Timeout Timeout { get => _timeout; set => _timeout = value ?? Timeout.Default; } + + /// 获取当前的 SqlConnection 对象。 + public IDbConnection Connection { get => _conn; } + + /// + public bool Online { get => _conn == null ? false : (_conn.State == ConnectionState.Open); } + + /// 连接字符串。 + public string ConnectionString { get => _str; } + + /// 连接数据库,若未连接则尝试连接,获取连接成功的状态。 + public string Connect() + { + if (_conn == null) + { + _str = NewConnectionString(); + _conn = NewConnection(); + _conn.ConnectionString = _str; + } + else + { + if (_conn.State == ConnectionState.Open) return null; + } + + try + { + _conn.Open(); + if (_conn.State == ConnectionState.Open) return null; + var message = $"连接后状态为 {_conn.State},无法验证打开状态。"; + Logger.Error(this, "Connect", message, _str); + return "连接失败," + message; + } + catch (Exception ex) + { + Logger.Error(this, "Connect", ex.GetType().Name, ex.Message, _str); + Close(); + return ex.Message; + } + } + + /// 关闭连接,并释放对象所占用的系统资源。 + public void Close() + { + if (_conn != null) + { + if (_transaction != null) + { + if (_autocommit) Commit(); + else Rollback(); + } + _conn.Close(); + _conn.Dispose(); + _conn = null; + } + } + + /// 关闭连接,释放对象所占用的系统资源,并清除连接信息。 + public void Dispose() { Close(); } + + #endregion + + #region transaction + + private IDbTransaction _transaction = null; + private bool _autocommit = false; + + /// + /// 启动事务,可指定事务锁定行为。 + /// Chaos
无法覆盖隔离级别更高的事务中的挂起的更改。
+ /// ReadCommitted
在正在读取数据时保持共享锁,以避免脏读,但是在事务结束之前可以更改数据,从而导致不可重复的读取或幻像数据。
+ /// ReadUncommitted
可以进行脏读,意思是说,不发布共享锁,也不接受独占锁。
+ /// RepeatableRead
在查询中使用的所有数据上放置锁,以防止其他用户更新这些数据。 防止不可重复的读取,但是仍可以有幻像行。
+ /// Serializable
在 System.Data.DataSet 上放置范围锁,以防止在事务完成之前由其他用户更新行或向数据集中插入行。
+ /// Snapshot
通过在一个应用程序正在修改数据时存储另一个应用程序可以读取的相同数据版本来减少阻止。 表示您无法从一个事务中看到在其他事务中进行的更改,即便重新查询也是如此。
+ /// Unspecified = -1
正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。
+ ///
+ /// 在连接的生命周期结束时未结束事务,指定 TRUE 将自动提交,指定 FALSE 将自动回滚。 + /// 指定事务锁定行为,不指定时将使用默认值。 + public string Begin(bool commit = false, Class isolation = null) + { + if (_transaction != null) return "已存在未完成的事务,无法再次启动。"; + var connect = Connect(); + if (connect.NotEmpty()) return $"无法启动事务:连接失败。(${connect})"; + try + { + _transaction = isolation ? _conn.BeginTransaction(isolation.Value) : _conn.BeginTransaction(); + _autocommit = commit; + return null; + } + catch (Exception ex) + { + Logger.Error(this, "Begin", ex.Message()); + return ex.Message(); + } + } + + /// 提交事务。 + public string Commit() + { + if (_transaction == null) return "事务不存在。"; + try + { + _transaction.Commit(); + RuntimeUtility.Dispose(_transaction); + _transaction = null; + return null; + } + catch (Exception ex) + { + RuntimeUtility.Dispose(_transaction); + _transaction = null; + Logger.Error(this, "Commit", ex.Message()); + return ex.Message(); + } + } + + /// 从挂起状态回滚事务。 + public string Rollback() + { + if (_transaction == null) return "事务不存在。"; + try + { + _transaction.Rollback(); + RuntimeUtility.Dispose(_transaction); + _transaction = null; + return null; + } + catch (Exception ex) + { + RuntimeUtility.Dispose(_transaction); + _transaction = null; + Logger.Error(this, "Rollback", ex.Message()); + return ex.Message(); + } + } + + #endregion + + #region ado + + /// 查询。 + public IQuery Query(string sql) => Query(sql, null); + + /// 查询。 + public IQuery Query(string sql, IEnumerable parameters) + { + if (TextUtility.IsEmpty(sql)) return new Query(false, "语句无效。"); + + var connected = Connect(); + if (connected.NotEmpty()) return new Query(false, connected); + + try + { + using (var command = NewCommand()) + { + command.Connection = _conn; + if (_timeout != null) command.CommandTimeout = Timeout.Query; + command.CommandText = sql; + if (parameters != null) + { + foreach (var parameter in parameters) + { + if (parameter != null) command.Parameters.Add(parameter); + } + } + + var ex = null as Exception; + var da = null as IDataAdapter; + try { da = NewDataAdapter(command); } + catch (Exception adapterEx) { ex = adapterEx; } + if (ex == null) + { + using (var ds = new DataSet()) + { + da.Fill(ds); + if (ds.Tables.Count > 0) + { + var tables = new DataTable[ds.Tables.Count]; + ds.Tables.CopyTo(tables, 0); + return new Query(tables, true); + } + else + { + Logger.Error(this, "Query", "查询结果不包含任何数据表。", sql); + return new Query(false, "查询结果不包含任何数据表。"); + } + } + } + else + { + Logger.Error(this, "Query", ex.GetType().FullName, ex.Message, sql); + return new Query(ex); + } + } + } + catch (Exception exception) + { + Logger.Error(this, "Query", exception.GetType().FullName, exception.Message, sql); + return new Query(exception); + } + } + + /// 执行。 + public IExecute Execute(string sql) => Execute(sql, null); + + /// 执行单条 Transact-SQL 语句,并加入参数。 + public IExecute Execute(string sql, IEnumerable parameters) + { + if (TextUtility.IsEmpty(sql)) return new Execute(false, "语句无效。"); + + var connected = Connect(); + if (connected.NotEmpty()) return new Execute(false, connected); + + var inTransaction = _transaction != null; + if (!inTransaction) Begin(); + try + { + + using (var command = NewCommand()) + { + command.Connection = _conn; + command.Transaction = (DbTransaction)_transaction; + if (Timeout != null) command.CommandTimeout = Timeout.Execute; + command.CommandText = sql; + if (parameters != null) + { + foreach (var parameter in parameters) + { + if (parameter != null) command.Parameters.Add(parameter); + } + } + var rows = command.ExecuteNonQuery(); + if (!inTransaction) Commit(); // todo 此处应该检查事务提交产生的错误。 + return new Execute(true, rows); + } + } + catch (Exception exception) + { + Logger.Error(this, "Execute", exception, sql); + if (!inTransaction) Rollback(); + return new Execute(exception); + } + } + + /// 输出查询结果的首列数据。 + 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(); + } + } + + #endregion + + #region orm + + /// 使用指定语句查询,获取查询结果。 + /// 要执行的 SQL 语句。 + /// 为 SQL 语句提供的参数。 + public Result Query(string sql, IEnumerable parameters = null) where T : class, new() => OrmHelper.As(Query(typeof(T), sql, parameters)); + + /// 使用指定语句查询,获取查询结果。 + /// 目标记录的类型。 + /// 要执行的 SQL 语句。 + /// 为 SQL 语句提供的参数。 + public Result Query(Type model, string sql, IEnumerable parameters = null) + { + if (_conn == null) return new Result("连接无效。"); + if (model == null) return new Result("数据模型类型无效。"); + if (string.IsNullOrEmpty(sql)) return new Result("SQL 语句无效。"); + + using (var query = Query(sql, parameters)) + { + var result = null as Result; + if (query.Success) + { + try + { + var array = OrmHelper.Fill(query, model); + result = new Result(array); + } + catch (Exception ex) + { + result = new Result(ex); + } + } + else + { + result = new Result(query.Message); + } + return result; + } + } + + #endregion + + #region static + + /// 对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。 + public static string Escape(string text, int bytes = 0) + { + if (text.IsEmpty()) return ""; + + var t = text ?? ""; + t = t.Replace("\\", "\\\\"); + t = t.Replace("'", "\\'"); + t = t.Replace("\n", "\\n"); + t = t.Replace("\r", "\\r"); + t = t.Replace("\b", "\\b"); + t = t.Replace("\t", "\\t"); + t = t.Replace("\f", "\\f"); + + if (bytes > 5) + { + if (t.Bytes(Encoding.UTF8).Length > bytes) + { + while (true) + { + t = t.Substring(0, t.Length - 1); + if (t.Bytes(Encoding.UTF8).Length <= (bytes - 4)) break; + } + t = t + " ..."; + } + } + + return t; + } + + /// 获取表名。 + protected static string Table() => Table(typeof(T)); + + /// 获取表名。 + protected static string Table(Type model) + { + if (model == null) throw new Exception($"无法从无效类型获取表名。"); + + var ts = TableStructure.Parse(model); + if (ts == null) throw new Exception($"无法从类型 {model.FullName} 获取表名。"); + + return ts.Name; + } + + #endregion + + #region derived + + /// 为 Ado 创建连接字符串。 + protected abstract string NewConnectionString(); + + /// 为 Ado 创建 IDbConnection 对象。 + protected abstract IDbConnection NewConnection(); + + /// 为 Ado 创建 IDbCommand 对象。 + protected abstract IDbCommand NewCommand(); + + /// 为 Ado 创建 IDataAdapter 对象。 + protected abstract IDataAdapter NewDataAdapter(IDbCommand command); + + // /// 为 Ado 创建 IDataParameter 对象。 + // protected abstract IDataParameter NewDataParameter(); + + #endregion + + #region initialization + + /// 查询数据库中的所有表名。 + public abstract string[] TableNames(); + + /// 查询数据库实例中的所有数据库名。 + public abstract string[] StoreNames(); + + /// 查询表中的所有列名。 + public abstract string[] ColumnNames(string tableName); + + /// 获取列信息。 + public abstract ColumnInfo[] ColumnsInfo(string tableName); + + /// 初始化指定类型,以创建表或增加字段。 + /// 错误信息。当成功时候返回空字符串。 + public string Initialize() where T : class, new() => Initialize(typeof(T)); + + #endregion + + #region IDbClientOrm + + /// 初始化指定类型,以创建表或增加字段。 + /// 要初始化的类型。 + /// 错误信息。当成功时候返回空字符串。 + public abstract string Initialize(Type model); + + /// 插入记录。 + /// 要插入的记录实体。 + /// 插入到指定表。当不指定时,由 record 类型决定。 + /// 错误信息。当成功时候返回空字符串。 + public abstract string Insert(object record, string table = null); + + /// 更新记录。 + /// 要插入的记录实体。 + /// 插入到指定表。当不指定时,由 record 类型决定。 + /// 错误信息。当成功时候返回空字符串。 + public abstract string Update(IRecord record, string table = null); + + /// 获取指定类型的主键,按 Flag 属性筛选。 + /// 要查询的类型。 + /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 + public abstract Result Keys(Type model, long flag = 0); + + /// 获取指定类型的主键,按 Flag 属性筛选。 + /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 + public abstract Result Keys(long flag = 0) where T : class, IRecord, new(); + + /// 获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。 + /// 目标记录的类型。 + /// 目标记录的主键。 + /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 + public abstract Result Get(Type model, string key, long flag = 0); + + /// 获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。 + /// 目标记录的主键。 + /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 + public abstract Result Get(string key, long flag = 0) where T : class, IRecord, new(); + + /// 查询所有记录。 + /// 目标记录的类型。 + /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 + public abstract Result Query(Type model, long flag = 0); + + /// 查询所有记录。 + /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 + public abstract Result Query(long flag = 0) where T : class, IRecord, new(); + + #endregion + + } + +} + +#endif diff --git a/Apewer.Source/Source/MySql.cs b/Apewer.Source/Source/MySql.cs index 1a577eb..679f4fd 100644 --- a/Apewer.Source/Source/MySql.cs +++ b/Apewer.Source/Source/MySql.cs @@ -221,7 +221,7 @@ namespace Apewer.Source } using (var ds = new DataSet()) { - using (var da = new MySqlDataAdapter(sql, _connection)) + using (var da = new MySqlDataAdapter(command)) { var name = "table_" + Guid.NewGuid().ToString("n"); da.Fill(ds, name); @@ -541,12 +541,12 @@ namespace Apewer.Source } /// - public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); + public Result Query(Type model, string sql, IEnumerable parameters = null) => OrmHelper.Query(this, model, sql, parameters); /// - public Result Query(string sql) where T : class, new() + public Result Query(string sql, IEnumerable parameters = null) where T : class, new() { - var query = Query(sql); + var query = Query(sql, parameters); if (!query.Success) return new Result(query.Message); var records = query.Fill(); query.Dispose(); diff --git a/Apewer.Source/Source/SqlClient.cs b/Apewer.Source/Source/SqlClient.cs index 9a9a81a..34b0034 100644 --- a/Apewer.Source/Source/SqlClient.cs +++ b/Apewer.Source/Source/SqlClient.cs @@ -560,12 +560,12 @@ namespace Apewer.Source } /// 获取按指定语句查询到的所有记录。 - public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); + public Result Query(Type model, string sql, IEnumerable parameters = null) => OrmHelper.Query(this, model, sql, parameters); /// 获取按指定语句查询到的所有记录。 - public Result Query(string sql) where T : class, new() + public Result Query(string sql, IEnumerable parameters = null) where T : class, new() { - var query = Query(sql); + var query = Query(sql, parameters); if (!query.Success) return new Result(query.Message); var records = query.Fill(); query.Dispose(); diff --git a/Apewer.Source/Source/SqlClientThin.cs b/Apewer.Source/Source/SqlClientThin.cs new file mode 100644 index 0000000..9d118dd --- /dev/null +++ b/Apewer.Source/Source/SqlClientThin.cs @@ -0,0 +1,528 @@ +#if DEBUG + +/* 2021.11.28 */ + +using Apewer; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Text; + +using static Apewer.Source.OrmHelper; +using System.Data.SqlClient; + +#if NETFRAMEWORK +using System.Data.Sql; +#endif + +namespace Apewer.Source +{ + + /// + [Serializable] + + public class SqlClientThin : DbClient, IDbClient + { + + #region + + string _str = null; + + /// 使用连接字符串创建数据库连接实例。 + public SqlClientThin(string connectionString, Timeout timeout = null) : base(timeout) + { + _str = connectionString ?? ""; + } + + /// 使用连接凭据创建数据库连接实例。 + public SqlClientThin(string address, string store, string user, string pass, Timeout timeout = null) : base(timeout) + { + if (timeout == null) timeout = Timeout.Default; + + 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; "; + else + { + cs += $"user id = {u}; "; + if (!string.IsNullOrEmpty(p)) cs += $"password = {p}; "; + } + cs += $"connection timeout = {timeout.Connect}; "; + + _str = cs; + } + + /// 为 Ado 创建连接字符串。 + protected override string NewConnectionString() => _str; + + /// 为 Ado 创建 IDbConnection 对象。 + protected override IDbConnection NewConnection() => new SqlConnection(); + + /// 为 Ado 创建 IDbCommand 对象。 + protected override IDbCommand NewCommand() => new SqlCommand(); + + /// 为 Ado 创建 IDataAdapter 对象。 + protected override IDataAdapter NewDataAdapter(IDbCommand command) => new SqlDataAdapter((SqlCommand)command); + + #endregion + + #region ORM + + /// 查询数据库中的所有表名。 + public override string[] TableNames() => TextColumn("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[] ColumnNames(string tableName) => TextColumn($"select [name] from [syscolumns] where [id] = object_id('{tableName}'); "); + + /// 获取列信息。 + public override ColumnInfo[] ColumnsInfo(string tableName) + { + if (tableName.IsEmpty()) throw new ArgumentNullException(nameof(tableName)); + var sql = $"select name, xtype, length from syscolumns where id = object_id('{tableName}') "; + using (var query = Query(sql)) + { + var ab = new ArrayBuilder(); + for (var i = 0; i < query.Rows; i++) + { + var info = new ColumnInfo(); + info.Name = query.Text(i, "name"); + info.Type = XType(query.Int32(i, "xtype")); + info.Length = query.Int32(i, "length"); + ab.Add(info); + } + return ab.Export(); + } + } + + /// 创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。 + public override string Initialize(Type model) + { + var structure = TableStructure.Parse(model); + if (structure == null) return "无法解析记录模型。"; + + // 连接数据库。 + var connect = Connect(); + if (connect.NotEmpty()) return connect; + + // 检查现存表。 + var exists = false; + var tables = TableNames(); + if (tables.Length > 0) + { + var lower = structure.Name.ToLower(); + foreach (var table in tables) + { + if (TextUtility.IsBlank(table)) continue; + if (table.ToLower() == lower) + { + exists = true; + break; + } + } + } + + if (exists) + { + // 获取已存在的列名。 + var columns = ColumnNames(structure.Name); + if (columns.Length > 0) + { + var lower = new List(); + foreach (var column in columns) + { + if (TextUtility.IsBlank(column)) continue; + lower.Add(column.ToLower()); + } + columns = lower.ToArray(); + } + + // 增加列。 + foreach (var column in structure.Columns) + { + // 检查 Independent 特性。 + if (structure.Independent && column.Independent) continue; + + // 去重。 + var lower = column.Field.ToLower(); + if (columns.Contains(lower)) continue; + + var type = Declaration(column); + if (string.IsNullOrEmpty(type)) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); + + var sql = TextUtility.Merge("alter table [", structure.Name, "] add ", type, "; "); + var execute = Execute(sql); + if (execute.Success == false) return execute.Message; + } + return TextUtility.Empty; + } + else + { + var sqlcolumns = new List(); + foreach (var column in structure.Columns) + { + // 检查 Independent 特性。 + if (structure.Independent && column.Independent) continue; + + var type = Declaration(column); + if (!column.Independent && column.Property.Name == "Key") type = type + " primary key"; + + if (string.IsNullOrEmpty(type)) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); + sqlcolumns.Add(type); + } + var sql = TextUtility.Merge("create table [", structure.Name, "](", string.Join(", ", sqlcolumns.ToArray()), "); "); + var execute = Execute(sql); + if (execute.Success) return TextUtility.Empty; + return execute.Message; + } + } + + /// 插入记录。返回错误信息。 + public override string Insert(object record, string table = null) + { + if (record == null) return "参数无效。"; + FixProperties(record); + + var structure = TableStructure.Parse(record.GetType()); + if (structure == null) return "无法解析记录模型。"; + if (string.IsNullOrEmpty(table)) table = structure.Name; + if (string.IsNullOrEmpty(table)) return "表名称无效。"; + + var ps = structure.CreateParameters(record, Parameter); + var psc = ps.Length; + if (psc < 1) return "数据模型不包含字段。"; + + var names = new List(psc); + var values = new List(psc); + foreach (var column in ps) + { + //names.Add(TextGenerator.Merge("[", column, "]")); + names.Add(TextUtility.Merge(column)); + values.Add("@" + column); + } + var sb = new StringBuilder(); + sb.Append("insert into [", table, "](", string.Join(", ", names.ToArray()), ") "); + sb.Append("values(", string.Join(", ", values.ToArray()), "); "); + var sql = sb.ToString(); + + var execute = Execute(sql, ps); + if (execute.Success) return TextUtility.Empty; + return execute.Message; + } + + /// 更新记录,实体中的 Key 属性不被更新。返回错误信息。 + /// 无法更新带有 Independent 特性的模型(缺少 Key 属性)。 + public override string Update(IRecord record, string table = null) + { + if (record == null) return "参数无效。"; + FixProperties(record); + SetUpdated(record); + + var structure = TableStructure.Parse(record.GetType()); + if (structure == null) return "无法解析记录模型。"; + if (structure.Independent) return "无法更新带有 Independent 特性的模型。"; + if (string.IsNullOrEmpty(table)) table = structure.Name; + if (string.IsNullOrEmpty(table)) return "表名称无效。"; + + var ps = structure.CreateParameters(record, Parameter, "_key"); + var psc = ps.Length; + if (psc < 1) return "数据模型不包含字段。"; + + var items = new List(); + foreach (var p in ps) + { + var pn = p.ParameterName; + items.Add(TextUtility.Merge("[", pn, "] = @", pn)); + } + var key = record.Key.SafeKey(); + var sql = TextUtility.Merge("update [", table, "] set ", string.Join(", ", items.ToArray()), " where [_key]='", key, "'; "); + + var execute = Execute(sql, ps); + if (execute.Success) return TextUtility.Empty; + return execute.Message; + } + + /// 获取记录。 + public override Result Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => + { + if (flag == 0) return $"select * from [{tn}]; "; + return $"select * from [{tn}] where _flag={flag}; "; + }); + + /// 获取记录。 + public override Result Query(long flag = 0) => OrmHelper.Query(this, (tn) => + { + if (flag == 0) return $"select * from [{tn}]; "; + return $"select * from [{tn}] where _flag={flag}; "; + }); + + /// 获取具有指定 Key 的记录。 + public override Result Get(Type model, string key, long flag = 0) => OrmHelper.Get(this, model, key, (tn, sk) => + { + if (flag == 0) return $"select top 1 * from [{tn}] _key='{sk}'; "; + return $"select top 1 * from [{tn}] where _key='{sk}' and _key='{sk}'; "; + }); + + /// 获取具有指定 Key 的记录。 + public override Result Get(string key, long flag = 0) => OrmHelper.Get(this, key, (tn, sk) => + { + if (flag == 0) return $"select top 1 * from [{tn}] _key='{sk}'; "; + return $"select top 1 * from [{tn}] where _key='{sk}' and _key='{sk}'; "; + }); + + /// 查询有效的 Key 值。 + public override Result Keys(Type model, long flag = 0) => OrmHelper.Keys(this, model, (tn) => + { + if (flag == 0) return $"select _key from [{tn}]; "; + return $"select _key from [{tn}] where _flag={flag}; "; + }); + + /// 查询有效的 Key 值。 + public override Result Keys(long flag = 0) => Keys(typeof(T), flag); + + #endregion + + #region public static + +#if NET20 || NET40 + + /// 枚举本地网络中服务器的名称。 + public static SqlServerSource[] EnumerateServer() + { + var list = new List(); + + // 表中列名:ServerName、InstanceName、IsClustered、Version。 + using (var query = new Query(SqlDataSourceEnumerator.Instance.GetDataSources())) + { + for (int i = 0; i < query.Rows; i++) + { + var item = new SqlServerSource(); + item.ServerName = query.Text(i, "ServerName"); + list.Add(item); + } + } + return list.ToArray(); + } + +#endif + + /// 指定的连接凭据是否符合连接要求,默认指定 master 数据库。 + public static bool Proven(string address, string user, string pass) => Proven(address, "master", user, pass); + + /// 指定的连接凭据是否符合连接要求。 + public static bool Proven(string address, string store, string user, string pass) + { + var a = string.IsNullOrEmpty(address); + var s = string.IsNullOrEmpty(store); + var u = string.IsNullOrEmpty(user); + var p = string.IsNullOrEmpty(pass); + if (a) return false; + if (s) return false; + if (u && !p) return false; + return true; + } + + /// 创建参数。 + /// + /// + static SqlParameter Parameter(Parameter parameter) + { + if (parameter == null) throw new InvalidOperationException("参数无效。"); + return Parameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value); + } + + /// 创建参数。 + public static SqlParameter Parameter(string name, ColumnType type, int size, object value) + { + var vname = TextUtility.Trim(name); + if (TextUtility.IsBlank(vname)) return null; + + var vtype = SqlDbType.BigInt; + switch (type) + { + case ColumnType.Bytes: + vtype = SqlDbType.Image; + break; + case ColumnType.Integer: + vtype = SqlDbType.BigInt; + break; + case ColumnType.Float: + vtype = SqlDbType.Float; + break; + case ColumnType.DateTime: + vtype = SqlDbType.DateTime; + break; + case ColumnType.VarChar: + case ColumnType.VarChar191: + case ColumnType.VarCharMax: + vtype = SqlDbType.VarChar; + break; + case ColumnType.NVarChar: + case ColumnType.NVarChar191: + case ColumnType.NVarCharMax: + vtype = SqlDbType.NVarChar; + break; + case ColumnType.Text: + vtype = SqlDbType.Text; + break; + case ColumnType.NText: + vtype = SqlDbType.NText; + break; + default: + throw new InvalidOperationException(TextUtility.Merge("类型 ", type.ToString(), " 不受支持。")); + } + + var vsize = size; + switch (type) + { + case ColumnType.VarChar: + vsize = NumberUtility.Restrict(vsize, 0, 8000); + break; + case ColumnType.NVarChar: + vsize = NumberUtility.Restrict(vsize, 0, 4000); + break; + case ColumnType.VarChar191: + case ColumnType.NVarChar191: + vsize = NumberUtility.Restrict(vsize, 0, 191); + break; + default: + vsize = 0; + break; + } + + var vvalue = value; + if (vvalue is string && vvalue != null && vsize > 0) + { + vvalue = TextUtility.Left((string)vvalue, vsize); + } + + var parameter = new SqlParameter(); + parameter.ParameterName = vname; + parameter.SqlDbType = vtype; + parameter.Value = vvalue; + if (vsize > 0) parameter.Size = vsize; + return parameter; + } + + /// 创建参数。 + public static SqlParameter Parameter(string name, SqlDbType type, int size, object value) + { + if (value is string && value != null && size > 0) + { + value = TextUtility.Left((string)value, (int)size); + } + + var p = new SqlParameter(); + p.ParameterName = name ?? ""; + p.SqlDbType = type; + p.Size = size; + p.Value = value; + return p; + } + + /// 创建参数。 + public static SqlParameter Parameter(string name, SqlDbType type, object value) + { + var p = new SqlParameter(); + p.ParameterName = name ?? ""; + p.SqlDbType = type; + p.Value = value; + return p; + } + + static string Declaration(ColumnAttribute column) + { + var type = TextUtility.Empty; + var vcolumn = column; + var length = Math.Max(0, vcolumn.Length); + switch (vcolumn.Type) + { + case ColumnType.Integer: + type = "bigint"; + break; + case ColumnType.Float: + type = "float"; + break; + case ColumnType.Bytes: + type = "image"; + break; + case ColumnType.DateTime: + type = "datetime"; + break; + case ColumnType.VarChar: + type = TextUtility.Merge("varchar(", Math.Min(8000, length).ToString(), ")"); + break; + case ColumnType.VarChar191: + type = TextUtility.Merge("varchar(191)"); + break; + case ColumnType.VarCharMax: + type = TextUtility.Merge("varchar(max)"); + break; + case ColumnType.Text: + type = TextUtility.Merge("text"); + break; + case ColumnType.NVarChar: + type = TextUtility.Merge("nvarchar(", Math.Min(4000, length).ToString(), ")"); + break; + case ColumnType.NVarChar191: + type = TextUtility.Merge("nvarchar(191)"); + break; + case ColumnType.NVarCharMax: + type = TextUtility.Merge("nvarchar(max)"); + break; + case ColumnType.NText: + type = TextUtility.Merge("ntext"); + break; + default: + return TextUtility.Empty; + } + return TextUtility.Merge("[", vcolumn.Field, "] ", type); + } + + static string XType(int xtype) + { + switch (xtype) + { + case 34: return "image"; + case 35: return "text"; + case 36: return "uniqueidentifier"; + case 48: return "tinyint"; + case 52: return "smallint"; + case 56: return "int"; + case 58: return "smalldatetime"; + case 59: return "real"; + case 60: return "money"; + case 61: return "datetime"; + case 62: return "float"; + case 98: return "sql_variant"; + case 99: return "ntext"; + case 104: return "bit"; + case 106: return "decimal"; + case 108: return "numeric"; + case 122: return "smallmoney"; + case 127: return "bigint"; + case 165: return "varbinary"; + case 167: return "varchar"; + case 173: return "binary"; + case 175: return "char"; + case 189: return "timestamp"; + case 231: return "nvarchar"; + case 239: return "nchar"; + case 241: return "xml"; + } + return null; + } + + #endregion + + } + +} + +#endif diff --git a/Apewer.Source/Source/Sqlite.cs b/Apewer.Source/Source/Sqlite.cs index 6329560..897361e 100644 --- a/Apewer.Source/Source/Sqlite.cs +++ b/Apewer.Source/Source/Sqlite.cs @@ -210,7 +210,7 @@ namespace Apewer.Source } using (var dataset = new DataSet()) { - using (var da = new SQLiteDataAdapter(sql, _db)) + using (var da = new SQLiteDataAdapter(command)) { var name = "table_" + Guid.NewGuid().ToString("n"); da.Fill(dataset, name); @@ -506,10 +506,10 @@ namespace Apewer.Source } /// 获取按指定语句查询到的所有记录。 - public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); + public Result Query(Type model, string sql, IEnumerable parameters = null) => OrmHelper.Query(this, model, sql, parameters); /// 获取按指定语句查询到的所有记录。 - public Result Query(string sql) where T : class, new() + public Result Query(string sql, IEnumerable parameters = null) where T : class, new() { var query = Query(sql); if (!query.Success) return new Result(query.Message); diff --git a/Apewer.Web/WebSocket/ChatServer.cs b/Apewer.Web/WebSocket/ChatServer.cs index 5557cec..db99b1d 100644 --- a/Apewer.Web/WebSocket/ChatServer.cs +++ b/Apewer.Web/WebSocket/ChatServer.cs @@ -144,10 +144,10 @@ namespace Apewer.WebSocket _command = new UdpServer(); _command.Port = _commandport; - _command.Excepted += (s, e) => RaiseExcepted(e); - _command.Quitted += (s, e) => RaiseConsole("命令服务已退出。"); - _command.Started += (s, e) => RaiseConsole("命令服务已启动。"); - _command.Received += CommandReceived; + _command.Excepted += (s, ex) => RaiseExcepted(ex); + _command.Quitted += (s) => RaiseConsole("命令服务已退出。"); + _command.Started += (s) => RaiseConsole("命令服务已启动。"); + _command.Received += (s, e) => CommandReceived(s, e.IP, e.Port, e.Bytes); _command.Start(); } } diff --git a/Apewer/ArrayBuilder.cs b/Apewer/ArrayBuilder.cs index 3730988..710e9d4 100644 --- a/Apewer/ArrayBuilder.cs +++ b/Apewer/ArrayBuilder.cs @@ -121,6 +121,31 @@ namespace Apewer foreach (var item in items) Add(item); } + /// 添加元素。 + /// 要添加的元素数组。 + /// buffer 的开始位置。 + /// buffer 的元素数量。 + /// + /// + public void Add(T[] buffer, int offset, int count) + { + if (buffer == null) throw new ArgumentNullException(nameof(buffer)); + var length = buffer.Length; + if (offset < 0 || offset >= length || count < 0) throw new ArgumentOutOfRangeException(); + if (offset + count > length) throw new ArgumentOutOfRangeException(); + + if (_capacity - _count < length) + { + _capacity = _count + length; + var temp = new T[_capacity]; + Array.Copy(_array, temp, _count); + _array = temp; + } + + Array.Copy(buffer, offset, _array, _count, count); + _count = _capacity; + } + /// 清空所有元素。 public void Clear() { diff --git a/Apewer/Network/SocketEndPoint.cs b/Apewer/Network/SocketEndPoint.cs index bc889ef..7b4a7ce 100644 --- a/Apewer/Network/SocketEndPoint.cs +++ b/Apewer/Network/SocketEndPoint.cs @@ -1,31 +1,30 @@ -using Apewer; -using System; +using System; using System.Collections.Generic; -using System.Net.Sockets; using System.Text; namespace Apewer.Network { - /// - public struct SocketEndPoint + /// 套接字的终端。 + [Serializable] + public sealed class SocketEndPoint { - internal SocketEndPoint(Socket socket, string ip, int port) - { - Socket = socket; - IP = ip ?? TextUtility.Empty; - Port = NumberUtility.Restrict(port, 0, ushort.MaxValue); - } + /// 对端 IP 地址。 + public string IP { get; set; } - /// - public Socket Socket { get; } + /// 对端端口。 + public int Port { get; set; } /// - public string IP { get; } + public SocketEndPoint() { } /// - public int Port { get; } + public SocketEndPoint(string ip, int port) + { + IP = ip; + Port = port; + } } diff --git a/Apewer/Network/SocketReceived.cs b/Apewer/Network/SocketReceived.cs new file mode 100644 index 0000000..65cb39b --- /dev/null +++ b/Apewer/Network/SocketReceived.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Network +{ + + /// 套接字数据接收模型。 + [Serializable] + public sealed class SocketReceived + { + + /// 对端 IP 地址。 + public string IP { get; set; } + + /// 对端端口。 + public int Port { get; set; } + + /// 对端数据。 + public byte[] Bytes { get; set; } + + /// + public SocketReceived() { } + + /// + public SocketReceived(string ip, int port, byte[] bytes) + { + IP = ip; + Port = port; + Bytes = bytes; + } + + } + +} diff --git a/Apewer/Internals/TcpBuffer.cs b/Apewer/Network/TcpBuffer.cs similarity index 70% rename from Apewer/Internals/TcpBuffer.cs rename to Apewer/Network/TcpBuffer.cs index df1249d..ced37f5 100644 --- a/Apewer/Internals/TcpBuffer.cs +++ b/Apewer/Network/TcpBuffer.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; using System.Net.Sockets; using System.Text; -namespace Apewer.Internals +namespace Apewer { + internal class TcpBuffer { - /// TCP 传输缓冲区大小。 - public const int Size= 8192; + // TCP 传输缓冲区大小。 + public const int Size = 8192; public Socket Socket; public byte[] Buffer; diff --git a/Apewer/Network/TcpClient.cs b/Apewer/Network/TcpClient.cs index 41ba09e..00e1296 100644 --- a/Apewer/Network/TcpClient.cs +++ b/Apewer/Network/TcpClient.cs @@ -19,33 +19,19 @@ namespace Apewer.Network #region event /// Exception。 - public event Event Excepted; + public Event Excepted { get; set; } /// 已发送数据。 - public event Event Sent; + public Event Sent { get; set; } /// 已接收数据。 - public event Event Received; + public Event Received { get; set; } /// 已连接。 - public event Event Connected; + public Event Connected { get; set; } /// 已断开。 - public event Event Closed; - - #region raise - - private void RaiseConnected() { if (Connected != null) Connected(this, new EventArgs()); } - - private void RaiseClosed() { if (Closed != null) Closed(this, new EventArgs()); } - - private void RaiseSent(byte[] value) { if (Sent != null) Sent(this, value); } - - private void RaiseReceived(byte[] value) { if (Received != null) Received(this, value); } - - private void RaiseExcepted(Exception value) { if (Excepted != null) Excepted(this, value); } - - #endregion + public Event Closed { get; set; } #endregion @@ -59,23 +45,25 @@ namespace Apewer.Network // private AutoResetEvent _are = null; private bool _break = false; - private int _timeout = 1000; + private int _timeout = 0; private string _localip, _remoteip; private int _localport = 0, _remoteport = 0; private bool _background = true; + /// + internal object Tag { get; set; } + /// 构造函数。 public TcpClient() { RemoteIP = "127.0.0.1"; RemotePort = 0; - Timeout = 1000; } /// 构造函数。 - public TcpClient(string ip, int port, int timeout = 1000) + public TcpClient(string ip, int port, int timeout = 0) { RemoteIP = ip; RemotePort = port; @@ -94,7 +82,7 @@ namespace Apewer.Network { _background = value; try { if (_listener != null) _listener.IsBackground = value; } - catch (Exception ex) { RaiseExcepted(ex); } + catch (Exception ex) { Excepted?.Invoke(this, ex); } } } @@ -104,10 +92,10 @@ namespace Apewer.Network get { return _remoteip; } set { - string vip = value; - if (!NetworkUtility.IsIP(vip)) vip = NetworkUtility.Resolve(vip); - if (!NetworkUtility.IsIP(vip)) vip = "127.0.0.1"; - _remoteip = vip; + string ip = value; + if (!NetworkUtility.IsIP(ip)) ip = NetworkUtility.Resolve(ip); + if (!NetworkUtility.IsIP(ip)) ip = "127.0.0.1"; + _remoteip = ip; } } @@ -117,10 +105,10 @@ namespace Apewer.Network get { return _remoteport; } set { - int vport = value; - if (vport < 0) vport = 0; - if (vport > 65535) vport = 65535; - _remoteport = vport; + int port = value; + if (port < 0) port = 0; + if (port > 65535) port = 65535; + _remoteport = port; } } @@ -137,10 +125,10 @@ namespace Apewer.Network get { return _localport; } private set { - int vport = value; - if (vport < 0) vport = 0; - if (vport > 65535) vport = 65535; - _localport = vport; + int port = value; + if (port < 0) port = 0; + if (port > 65535) port = 65535; + _localport = port; } } @@ -148,18 +136,24 @@ namespace Apewer.Network public int Timeout { get { return _timeout; } - set { _timeout = (value > 0) ? value : 1; } + set { _timeout = (value > 0) ? value : 0; } } /// 开始连接,并初始化发送队列。 - public void Start() + public void Start(bool inBackground = false) { Close(false); _queue = new Queue(); - _provider = new Thread(Provider); - _provider.IsBackground = true; - _provider.Start(); + if (!inBackground) + { + _provider = new Thread(Provider); + _provider.IsBackground = true; + _provider.Start(); + return; + } + + Provider(); // _are.WaitOne(1000); } @@ -173,7 +167,7 @@ namespace Apewer.Network CloseQueue(); _queue = null; // _are = null; - if (@event) RaiseClosed(); + if (@event) Closed?.Invoke(this); } /// 向服务端发送数据。 @@ -187,8 +181,8 @@ namespace Apewer.Network } else { - var vse = new SocketException(10057); - RaiseExcepted(vse); + var ex = new SocketException(10057); + Excepted?.Invoke(this, ex); return false; } } @@ -199,7 +193,7 @@ namespace Apewer.Network get { try { if (_socket != null) return _socket.Connected; } - catch (Exception ex) { RaiseExcepted(ex); } + catch (Exception ex) { Excepted?.Invoke(this, ex); } return false; } } @@ -210,8 +204,8 @@ namespace Apewer.Network private void Listener() { - var vb = new byte[TcpBuffer.Size]; - var vf = 0; + var buffer = new byte[TcpBuffer.Size]; + var length = 0; while (true) { try @@ -219,13 +213,12 @@ namespace Apewer.Network if (_socket.Poll(50, SelectMode.SelectWrite)) { _socket.Blocking = true; - vf = _socket.Receive(vb); - if (vf > 0) + length = _socket.Receive(buffer); + if (length > 0) { - var vms = new MemoryStream(vb); - vms.SetLength(vf); - RaiseReceived(vms.ToArray()); - vms.Dispose(); + var bytes = new byte[length]; + Array.Copy(buffer, bytes, length); + Received?.Invoke(this, bytes); } else { @@ -235,12 +228,12 @@ namespace Apewer.Network } catch (SocketException ex) { - if (ex.ErrorCode != 10060) RaiseExcepted(ex); + if (ex.ErrorCode != 10060) Excepted?.Invoke(this, ex); if (ex.ErrorCode == 10054) break; } catch (Exception ex) { - RaiseExcepted(ex); + Excepted?.Invoke(this, ex); // break; } } @@ -258,15 +251,15 @@ namespace Apewer.Network CloseThread(ref _provider); CloseSocket(); CloseQueue(); - RaiseClosed(); + Closed?.Invoke(this); } private void Provider() { _break = false; _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - _socket.SendTimeout = _timeout; - _socket.ReceiveTimeout = _timeout; + if (_timeout > 0) _socket.SendTimeout = _timeout; + if (_timeout > 0) _socket.ReceiveTimeout = _timeout; //_are = new AutoResetEvent(false); try @@ -277,13 +270,13 @@ namespace Apewer.Network { _socket.SendBufferSize = TcpBuffer.Size; _socket.ReceiveBufferSize = TcpBuffer.Size; - var vrep = (IPEndPoint)_socket.RemoteEndPoint; - var vlep = (IPEndPoint)_socket.LocalEndPoint; - _remoteip = vrep.Address.ToString(); - _localip = vlep.Address.ToString(); - _localport = vlep.Port; + var rep = (IPEndPoint)_socket.RemoteEndPoint; + var lep = (IPEndPoint)_socket.LocalEndPoint; + _remoteip = rep.Address.ToString(); + _localip = lep.Address.ToString(); + _localport = lep.Port; - RaiseConnected(); + Connected?.Invoke(this); _listener = new Thread(Listener); _listener.Start(); // _are.Set(); @@ -296,7 +289,7 @@ namespace Apewer.Network } catch (Exception ex) { - RaiseExcepted(ex); + Excepted?.Invoke(this, ex); } while ((!_break) && (_socket != null)) @@ -304,17 +297,17 @@ namespace Apewer.Network if (_queue.Count > 0) { - var vb = _queue.Dequeue(); - if (vb.Length > 0) + var bytes = _queue.Dequeue(); + if (bytes.Length > 0) { try { - _socket.Send(vb); - RaiseSent(vb); + _socket.Send(bytes); + Sent?.Invoke(this, bytes); } catch (Exception ex) { - RaiseExcepted(ex); + Excepted?.Invoke(this, ex); } } else @@ -334,8 +327,8 @@ namespace Apewer.Network _socket.Disconnect(false); _socket.Close(); } - catch (SocketException ex) { RaiseExcepted(ex); } - catch (Exception ex) { RaiseExcepted(ex); } + catch (SocketException ex) { Excepted?.Invoke(this, ex); } + catch (Exception ex) { Excepted?.Invoke(this, ex); } _socket = null; } } diff --git a/Apewer/Internals/TcpInstance.cs b/Apewer/Network/TcpInstance.cs similarity index 74% rename from Apewer/Internals/TcpInstance.cs rename to Apewer/Network/TcpInstance.cs index 9c0eeda..15e2d1a 100644 --- a/Apewer/Internals/TcpInstance.cs +++ b/Apewer/Network/TcpInstance.cs @@ -7,7 +7,7 @@ using System.Net.Sockets; using System.Text; using System.Threading; -namespace Apewer.Internals +namespace Apewer { internal class TcpInstance @@ -25,11 +25,11 @@ namespace Apewer.Internals private int _length = 0; private byte[] _data; - public TcpInstance(TcpServer argParent, Socket argServer, Socket argClient) + public TcpInstance(TcpServer parent, Socket server, Socket client) { - _parent = argParent; - _server = argServer; - _client = argClient; + _parent = parent; + _server = server; + _client = client; _ep = (IPEndPoint)_client.RemoteEndPoint; _ip = _ep.Address.ToString(); _port = _ep.Port; @@ -47,20 +47,20 @@ namespace Apewer.Internals catch (Exception argex) { _parent.RaiseExcepted(argex); } } - private void Callback(IAsyncResult argAr) + private void Callback(IAsyncResult ar) { try { - var vm = (TcpBuffer)argAr.AsyncState; - if (vm.Socket.Connected) + var buffer = (TcpBuffer)ar.AsyncState; + if (buffer.Socket.Connected) { - _length = vm.Socket.EndReceive(argAr); + _length = buffer.Socket.EndReceive(ar); if (_length > 0) { _data = new byte[_length]; - Array.Copy(vm.Buffer, 0, _data, 0, _length); + Array.Copy(buffer.Buffer, 0, _data, 0, _length); _parent.RaiseReceived(_ip, _port, _data); - vm.Socket.BeginReceive(vm.Buffer, 0, vm.Buffer.Length, SocketFlags.None, Callback, vm); + buffer.Socket.BeginReceive(buffer.Buffer, 0, buffer.Buffer.Length, SocketFlags.None, Callback, buffer); } else { @@ -71,7 +71,7 @@ namespace Apewer.Internals } if (_ep != null) { - _parent.Remove(_ip + ":" + _port.ToString()); + _parent.Remove(_ip + "-" + _port.ToString()); _parent.RaiseClosed(_ip, _port); } } @@ -81,9 +81,9 @@ namespace Apewer.Internals if (!string.IsNullOrEmpty(_ip)) _parent.Close(_ip, _port); } } - catch (Exception argex) + catch (Exception ex) { - _parent.RaiseExcepted(argex); + _parent.RaiseExcepted(ex); if (_client != null) { if (_client.Connected) _client.Close(); diff --git a/Apewer/Network/TcpProxy.cs b/Apewer/Network/TcpProxy.cs new file mode 100644 index 0000000..cea1c11 --- /dev/null +++ b/Apewer/Network/TcpProxy.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Security; +using System.Text; +using System.Threading; + +namespace Apewer.Network +{ + + /// TCP 端口代理。 + public class TcpProxy + { + + IPEndPoint _local; + Socket _listen; + Thread _thread; + int _backlog; + int _port; + + Func _remote; + + /// 本地端口已连接。 + public bool Connected { get => _listen == null ? false : _listen.Connected; } + + /// 本地已监听的端口。 + public int Port { get => _port; } + + /// 监听本地端口以启动代理。 + /// 本地监听端口。 + /// 获取要连接的远程端口,无法获取远程端口时应返回 NULL 值。 + /// 挂起连接队列的最大长度 + /// + /// + /// + /// + public TcpProxy(IPEndPoint local, Func remote, int backlog = 10000) + { + Start(local, remote, backlog); + } + + void Start(IPEndPoint local, Func remote, int backlog = 10000) + { + if (local == null) throw new ArgumentNullException(nameof(local)); + if (remote == null) throw new ArgumentNullException(nameof(remote)); + if (backlog < 1) throw new ArgumentOutOfRangeException(nameof(backlog)); + + _local = local; + _remote = remote; + _backlog = backlog; + + var server = new Socket(_local.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + server.Bind(_local); + server.Listen(_backlog); + _listen = server; + if (server.LocalEndPoint is IPEndPoint lep) _port = lep.Port; + + _thread = new Thread(Listen); + _thread.IsBackground = true; + _thread.Start(); + } + + /// 停止代理。 + public void Stop() + { + Close(_listen); + } + + void Listen() + { + while (true) + { + if (_listen == null) break; + Socket socket1 = null; + IPEndPoint remote = null; + try + { + socket1 = _listen.Accept(); + remote = _remote(socket1.RemoteEndPoint as IPEndPoint); + } + catch { } + if (socket1 == null || remote == null) continue; + + Socket socket2 = null; + try + { + socket2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socket2.Connect(remote); + ThreadPool.QueueUserWorkItem(Handler, new Socket[] { socket1, socket2 }); + ThreadPool.QueueUserWorkItem(Handler, new Socket[] { socket2, socket1 }); + } + catch + { + Close(socket1); + Close(socket2); + } + } + } + + void Handler(object obj) + { + var tuple = (Socket[])obj; + var src = tuple[0]; + var dst = tuple[1]; + var buffer = new byte[1024]; + while (true) + { + try + { + int count = src.Receive(buffer, buffer.Length, SocketFlags.None); + if (count < 1) break; + dst.Send(buffer, count, SocketFlags.None); + } + catch { break; } + } + Close(src); + Close(dst); + } + + static void Close(Socket socket) + { + if (socket == null) return; + try { socket.Close(); } catch { } + } + + } + +} diff --git a/Apewer/Network/TcpServer.cs b/Apewer/Network/TcpServer.cs index 0b65002..5421521 100644 --- a/Apewer/Network/TcpServer.cs +++ b/Apewer/Network/TcpServer.cs @@ -12,28 +12,28 @@ namespace Apewer.Network { /// TCP 服务端。 - public class TcpServer + internal class TcpServer { #region event /// Exception。 - public event Event Excepted; + public Event Excepted { get; set; } /// 服务端已启动。 - public event SocketEndPointEventHandler Started; + public Event Started { get; set; } /// 服务端已关闭。 - public event SocketEndPointEventHandler Quitted; + public Event Quitted { get; set; } /// 客户端已连接。 - public event SocketEndPointEventHandler Connected; + public Event Connected { get; set; } /// 客户端已断开。 - public event SocketEndPointEventHandler Closed; + public Event Closed { get; set; } /// 已收到客户端数据。 - public event SocketReceivedEventHandler Received; + public Event Received { get; set; } #region raise @@ -48,33 +48,34 @@ namespace Apewer.Network { var ip = _endpoint == null ? "" : _endpoint.Address.ToString(); var port = _endpoint == null ? 0 : _endpoint.Port; - Started(this, ip, port); + Started?.Invoke(this, new SocketEndPoint(ip, port)); } } internal void RaiseConnected(string ip, int port) { - if (Connected != null) Connected(this, ip, port); + Connected?.Invoke(this, new SocketEndPoint(ip, port)); } internal void RaiseClosed(string ip, int port) { - if (Closed != null) Closed(this, ip, port); + Closed?.Invoke(this, new SocketEndPoint(ip, port)); } internal void RaiseQuitted() { - if (Quitted != null) + var quitted = Quitted; + if (quitted != null) { var ip = _endpoint == null ? "" : _endpoint.Address.ToString(); var port = _endpoint == null ? 0 : _endpoint.Port; - Quitted(this, ip, port); + quitted(this, new SocketEndPoint(ip, port)); } } internal void RaiseReceived(string ip, int port, byte[] bytes) { - if (Received != null) Received(this, ip, port, bytes); + Received?.Invoke(this, new SocketReceived(ip, port, bytes)); } #endregion @@ -123,17 +124,17 @@ namespace Apewer.Network { get { - int vr = _port; - if (vr > 65535) vr = 65535; - if (vr < 0) vr = 0; - return vr; + int port = _port; + if (port > 65535) port = 65535; + if (port < 0) port = 0; + return port; } set { - int vport = value; - if (vport < 0) vport = 0; - if (vport > 65535) vport = 65535; - _port = vport; + int port = value; + if (port < 0) port = 0; + if (port > 65535) port = 65535; + _port = port; } } @@ -164,7 +165,7 @@ namespace Apewer.Network } /// 启动服务端。 - public bool Start() + public bool Start(bool inBackground = true) { try { @@ -176,20 +177,34 @@ namespace Apewer.Network _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.AcceptConnection, 1); _socket.ReceiveTimeout = Timeout; _socket.SendTimeout = Timeout; - _listener = new Thread(Listener); - _listener.IsBackground = Background; - _listener.Start(); - _state = _listener.IsAlive; - var vcep = (IPEndPoint)_socket.LocalEndPoint; - _port = vcep.Port; - RaiseStarted(); - return true; + + if (inBackground) + { + _listener = new Thread(Listener); + _listener.IsBackground = Background; + _listener.Start(); + _state = _listener.IsAlive; + var cep = (IPEndPoint)_socket.LocalEndPoint; + _port = cep.Port; + RaiseStarted(); + return true; + } + else + { + _state = true; + var cep = (IPEndPoint)_socket.LocalEndPoint; + _port = cep.Port; + RaiseStarted(); + Listener(); + return true; + } } catch (Exception ex) { _endpoint = null; RaiseExcepted(ex); if ((_socket != null) && _socket.Connected) _socket.Close(); + _state = false; Quit(); return false; } @@ -206,12 +221,12 @@ namespace Apewer.Network { if (_client != null) { - foreach (Socket vcs in _client.Values) + foreach (Socket socket in _client.Values) { try { - var vep = (IPEndPoint)vcs.RemoteEndPoint; - Close(vep.Address.ToString(), vep.Port); + var ep = (IPEndPoint)socket.RemoteEndPoint; + Close(ep.Address.ToString(), ep.Port); } catch (Exception ex) { @@ -224,19 +239,18 @@ namespace Apewer.Network /// 断开与指定客户端的连接。 public void Close(string ip, int port) { - int vport = port; - if (vport < 0) vport = 0; - if (vport > 65535) vport = 65535; + if (port < 0) port = 0; + if (port > 65535) port = 65535; if (!string.IsNullOrEmpty(ip)) { - var vck = ip + ":" + vport.ToString(); - var vcs = Client(ip, vport); + var ck = ip + "-" + port.ToString(); + var cs = Client(ip, port); try { - if (vcs != null) vcs.Close(); - Remove(vck); - RaiseClosed(ip, vport); + if (cs != null) cs.Close(); + Remove(ck); + RaiseClosed(ip, port); } catch (Exception ex) { @@ -248,8 +262,8 @@ namespace Apewer.Network /// 向所有客户端广播数据。 public void Send(byte[] bytes) { - var vl = bytes.Length; - if ((_client.Count > 0) && (vl > 0)) + var length = bytes.Length; + if ((_client.Count > 0) && (length > 0)) { foreach (Socket i in _client.Values) Send(bytes, i); } @@ -258,9 +272,8 @@ namespace Apewer.Network /// 向指定客户端发送数据。 public bool Send(byte[] bytes, string ip, int port) { - int vport = port; - if (vport < 0) vport = 0; - if (vport > 65535) vport = 65535; + if (port < 0) port = 0; + if (port > 65535) port = 65535; if (string.IsNullOrEmpty(ip)) return false; return Send(bytes, Client(ip, port)); @@ -326,12 +339,12 @@ namespace Apewer.Network private bool Send(byte[] bytes, Socket client) { - var vl = bytes.Length; - if ((client != null) && (vl > 0)) + var length = bytes.Length; + if ((client != null) && (length > 0)) { try { - return (client.Send(bytes, vl, SocketFlags.None) > 0) ? true : false; + return (client.Send(bytes, length, SocketFlags.None) > 0) ? true : false; } catch (Exception ex) { RaiseExcepted(ex); } } @@ -344,18 +357,18 @@ namespace Apewer.Network { try { - var vcs = _socket.Accept(); - if (vcs != null) + var socket = _socket.Accept(); + if (socket != null) { - var vep = (IPEndPoint)vcs.RemoteEndPoint; - var vck = vep.Address.ToString() + ":" + vep.Port.ToString(); - _client.Add(vck, vcs); - var vci = new TcpInstance(this, _socket, vcs); - var vct = new Thread(vci.Process); - vct.IsBackground = true; - vct.Name = vck; - vct.Start(); - RaiseConnected(vep.Address.ToString(), vep.Port); + var ep = (IPEndPoint)socket.RemoteEndPoint; + var key = ep.Address.ToString() + "-" + ep.Port.ToString(); + _client.Add(key, socket); + var instance = new TcpInstance(this, _socket, socket); + var thread = new Thread(instance.Process); + thread.IsBackground = true; + thread.Name = key; + thread.Start(); + RaiseConnected(ep.Address.ToString(), ep.Port); } } catch (Exception ex) @@ -368,13 +381,12 @@ namespace Apewer.Network private Socket Client(string ip, int port) { - int vport = port; - if (vport < 0) vport = 0; - if (vport > 65535) vport = 65535; + if (port < 0) port = 0; + if (port > 65535) port = 65535; try { - var vck = ip + ":" + vport.ToString(); - if (_client.ContainsKey(vck)) return (Socket)_client[vck]; + var ck = ip + "-" + port.ToString(); + if (_client.ContainsKey(ck)) return (Socket)_client[ck]; } catch (Exception ex) { diff --git a/Apewer/Network/UdpServer.cs b/Apewer/Network/UdpServer.cs index dbb2682..141077e 100644 --- a/Apewer/Network/UdpServer.cs +++ b/Apewer/Network/UdpServer.cs @@ -23,16 +23,16 @@ namespace Apewer.Network private int _port = 0; /// Exception。 - public event Event Excepted; + public Event Excepted { get; set; } /// 服务端已启动。 - public event EventHandler Started; + public Event Started { get; set; } /// 服务端已关闭。 - public event EventHandler Quitted; + public Event Quitted { get; set; } /// 已收到客户端数据。 - public event SocketReceivedEventHandler Received; + public Event Received { get; set; } /// 构造函数。 public UdpServer() @@ -90,7 +90,7 @@ namespace Apewer.Network { _udp.Close(); _udp = null; - if (Quitted != null) Quitted(this, new EventArgs()); + Quitted?.Invoke(this); } } @@ -100,13 +100,15 @@ namespace Apewer.Network { var ep = new IPEndPoint(IPAddress.Any, Port); _udp = new System.Net.Sockets.UdpClient(ep); - if (Started != null) Started(this, new EventArgs()); + Started?.Invoke(this); while (true) { - var vbytes = _udp.Receive(ref ep); - if ((Received != null) && (vbytes.Length > 0)) + var bytes = _udp.Receive(ref ep); + if ((Received != null) && (bytes.Length > 0)) { - Received(this, ep.Address.ToString(), ep.Port, vbytes); + var ip = ep.Address.ToString(); + var port = ep.Port; + Received?.Invoke(this, new SocketReceived(ip, ep.Port, bytes)); } // Thread.Sleep(1); } @@ -115,7 +117,7 @@ namespace Apewer.Network { if (Excepted != null) Excepted(this, ex); } - if (Quitted != null) Quitted(this, new EventArgs()); + Quitted.Invoke(this); } } diff --git a/Apewer/Network/_Delegate.cs b/Apewer/Network/_Delegate.cs deleted file mode 100644 index a36ea60..0000000 --- a/Apewer/Network/_Delegate.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Sockets; -using System.Text; - -namespace Apewer.Network -{ - - /// - public delegate void SocketReceivedEventHandler(object sender, string ip, int port, byte[] bytes); - - /// - public delegate void SocketEndPointEventHandler(object sender, string ip, int port); - - ///// - //public delegate void SocketExceptionCallback(object argSender, SocketException value); - - ///// 返回 Http Progress 对象。 - //public delegate void HttpProgressCallback(object argSender, HttpProgress argValue); - -} diff --git a/Apewer/NetworkUtility.cs b/Apewer/NetworkUtility.cs index b6d618f..503c0cf 100644 --- a/Apewer/NetworkUtility.cs +++ b/Apewer/NetworkUtility.cs @@ -255,7 +255,7 @@ namespace Apewer } /// 转换 IP 地址格式。 - public static IPEndPoint GetIPEndPoint(EndPoint endpoint) + public static IPEndPoint GetIPEndPoint(System.Net.EndPoint endpoint) { try { return (IPEndPoint)endpoint; } catch { return new IPEndPoint(0, 0); } @@ -323,6 +323,62 @@ namespace Apewer return HttpClient.SimpleForm(url, form, timeout); } + /// 获取 HTTP 状态的文本。 + public static string HttpStatusDescription(int code) + { + switch (code) + { + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 102: return "Processing"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 307: return "Temporary Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Request Entity Too Large"; + case 414: return "Request-Uri Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 426: return "Upgrade Required"; // RFC 2817 + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "Http Version Not Supported"; + case 507: return "Insufficient Storage"; + default: return null; + } + } + #endregion #region Port diff --git a/Apewer/RuntimeUtility.cs b/Apewer/RuntimeUtility.cs index 6b2a69a..afa3a61 100644 --- a/Apewer/RuntimeUtility.cs +++ b/Apewer/RuntimeUtility.cs @@ -734,7 +734,44 @@ namespace Apewer #endregion - #region Debug or Test + #region Evaluate + + /// 评估 Action 的执行时间,单位为毫秒。 + public static long Evaluate(Action action) + { + if (action == null) return 0; + var stopwatch = new Stopwatch(); + stopwatch.Start(); + action.Invoke(); + stopwatch.Stop(); + return stopwatch.ElapsedMilliseconds; + } + + #endregion + + #region Console + + /// 获取或设置控制台标题。 + public static string Title + { + get { try { return Console.Title; } catch { return null; } } + set { try { Console.Title = value; } catch { } } + } + + /// 设置在控制台中按 CTRL + C 时的方法。 + public static void CtrlCancel(Func exit) + { + const string postfix = " - 按 CTRL + C 可安全退出"; + var title = Title ?? ""; + if (!title.EndsWith(postfix)) + { + title = title + postfix; + Title = title; + } + + if (exit == null) return; + try { Console.CancelKeyPress += (s, e) => e.Cancel = !exit(); } catch { } + } /// 调用所有公共类,创建构造函数。 /// 要搜索类的程序集,指定为 NULL 时将获取当前程序集。 @@ -744,7 +781,7 @@ namespace Apewer public static void InvokePublicClass(Assembly assembly = null, bool catchException = false) { const string Title = "Invoing Public Class"; - Console.Title = Title; + RuntimeUtility.Title = Title; var before = DateTime.Now; Logger.Internals.Info(typeof(RuntimeUtility), "Started Invoke."); @@ -756,7 +793,7 @@ namespace Apewer if (!type.IsPublic) continue; if (!CanNew(type)) continue; Logger.Internals.Info(typeof(RuntimeUtility), "Invoke " + type.FullName); - Console.Title = type.FullName; + RuntimeUtility.Title = type.FullName; if (catchException) { @@ -774,7 +811,7 @@ namespace Apewer Activator.CreateInstance(type); } } - Console.Title = Title; + RuntimeUtility.Title = Title; var after = DateTime.Now; var span = after - before; @@ -788,16 +825,22 @@ namespace Apewer Logger.Internals.Info(typeof(RuntimeUtility), result); } - /// 评估 Action 的执行时间,单位为毫秒。 - public static long Evaluate(Action action) - { - if (action == null) return 0; - var stopwatch = new Stopwatch(); - stopwatch.Start(); - action.Invoke(); - stopwatch.Stop(); - return stopwatch.ElapsedMilliseconds; - } + #endregion + + #region System + +#if NETSTD || NETCORE + + /// 当前操作系统是 Windows。 + public static bool IsWindows { get => System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows); } + + /// 当前操作系统是 OS X 或 macOS。 + public static bool IsOSX { get => System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX); } + + /// 当前操作系统是 Linux。 + public static bool IsLinux { get => System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux); } + +#endif #endregion diff --git a/Apewer.Source/Source/ColumnInfo.cs b/Apewer/Source/ColumnInfo.cs similarity index 100% rename from Apewer.Source/Source/ColumnInfo.cs rename to Apewer/Source/ColumnInfo.cs diff --git a/Apewer/Source/DbClient.cs b/Apewer/Source/DbClient.cs deleted file mode 100644 index 906897a..0000000 --- a/Apewer/Source/DbClient.cs +++ /dev/null @@ -1,339 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Text; - -namespace Apewer.Source -{ - - /// - abstract class DbClient - { - - /// - public virtual Logger Logger { get; set; } - - #region Connection - - DbConnection _conn = null; - string _str = null; - - /// - public Timeout Timeout { get; set; } - - /// - public DbConnection Connection { get => _conn; } - - /// - public bool Online { get => _conn == null ? false : (_conn.State == ConnectionState.Open); } - - /// 连接字符串。 - public string ConnectionString { get => _str; } - - /// - public virtual string Connect() - { - if (_conn == null) - { - _str = GetConnectionString(); - _conn = NewConnection(); - _conn.ConnectionString = _str; - } - else - { - if (_conn.State == ConnectionState.Open) return null; - } - - try - { - _conn.Open(); - switch (_conn.State) - { - case ConnectionState.Open: return null; - default: return $"连接失败,当前处于 {_conn.State} 状态。"; - } - } - catch (Exception ex) - { - Logger.Error(this, "Connect", ex, _conn.ConnectionString); - Close(); - return ex.Message; - } - } - - /// - public void Close() - { - if (_conn != null) - { - if (_transaction != null) - { - if (_autocommit) Commit(); - else Rollback(); - } - _conn.Close(); - _conn.Dispose(); - _conn = null; - } - } - - /// - public void Dispose() { Close(); } - - #endregion - - #region Transaction - - private DbTransaction _transaction = null; - private bool _autocommit = false; - - /// 启动事务。 - public string Begin(bool commit = true) => Begin(commit, null); - - /// 启动事务。 - public string Begin(bool commit, Class isolation) - { - if (Connect() != null) return "未连接。"; - if (_transaction != null) return "存在已启动的事务,无法再次启动。"; - try - { - _transaction = isolation ? _conn.BeginTransaction(isolation.Value) : _conn.BeginTransaction(); - _autocommit = commit; - return null; - } - catch (Exception ex) - { - Logger.Error(this, "Commit", ex.Message()); - return ex.Message(); - } - } - - /// 提交事务。 - public string Commit() - { - if (_transaction == null) return "事务不存在。"; - try - { - _transaction.Commit(); - RuntimeUtility.Dispose(_transaction); - _transaction = null; - return null; - } - catch (Exception ex) - { - RuntimeUtility.Dispose(_transaction); - _transaction = null; - Logger.Error(this, "Commit", ex.Message()); - return ex.Message(); - } - } - - /// 从挂起状态回滚事务。 - public string Rollback() - { - if (_transaction == null) return "事务不存在。"; - try - { - _transaction.Rollback(); - RuntimeUtility.Dispose(_transaction); - _transaction = null; - return null; - } - catch (Exception ex) - { - RuntimeUtility.Dispose(_transaction); - _transaction = null; - Logger.Error(this, "Rollback", ex.Message()); - return ex.Message(); - } - } - - #endregion - - #region ADO - - /// 查询。 - public IQuery Query(string sql) => Query(sql, null); - - /// 查询。 - public IQuery Query(string sql, IEnumerable parameters) - { - if (TextUtility.IsBlank(sql)) return new Query(false, "语句无效。"); - var connected = Connect(); - if (connected.NotEmpty()) return new Query(false, connected); - - try - { - using (var command = NewCommand()) - { - command.Connection = _conn; - if (Timeout != null) command.CommandTimeout = Timeout.Query; - command.CommandText = sql; - if (parameters != null) - { - foreach (var parameter in parameters) - { - if (parameter != null) command.Parameters.Add(parameter); - } - } - using (var ds = new DataSet()) - { - using (var da = NewDataAdapter(sql)) - { - var name = "table_" + Guid.NewGuid().ToString("n"); - da.Fill(ds, name); - var table = ds.Tables[name]; - return new Query(table, true); - } - } - } - } - catch (Exception exception) - { - Logger.Error(this, "Query", exception, sql); - return new Query(exception); - } - } - - /// 执行。 - public IExecute Execute(string sql) => Execute(sql, null); - - /// 执行单条 Transact-SQL 语句,并加入参数。 - public IExecute Execute(string sql, IEnumerable parameters) - { - if (TextUtility.IsBlank(sql)) return Example.InvalidExecuteStatement; - - var connected = Connect(); - if (connected.NotEmpty()) return new Execute(false, connected); - - var inTransaction = _transaction != null; - if (!inTransaction) Begin(); - try - { - using (var command = NewCommand()) - { - command.Connection = _conn; - command.Transaction = (DbTransaction)_transaction; - if (Timeout != null) command.CommandTimeout = Timeout.Execute; - command.CommandText = sql; - if (parameters != null) - { - foreach (var parameter in parameters) - { - if (parameter != null) command.Parameters.Add(parameter); - } - } - var rows = command.ExecuteNonQuery(); - if (!inTransaction) Commit(); // todo 此处应该检查事务提交产生的错误。 - return new Execute(true, rows); - } - } - catch (Exception exception) - { - Logger.Error(this, "Execute", exception, sql); - if (!inTransaction) Rollback(); - return new Execute(exception); - } - } - - #endregion - - #region ORM - Query - - /// 查询记录。 - /// 记录模型。 - /// SQL 语句。 - public Result Query(Type model, string sql) - { - if (_conn == null) return new Result("连接无效。"); - if (model == null) return new Result("数据模型类型无效。"); - if (string.IsNullOrEmpty(sql)) return new Result("SQL 语句无效。"); - - var query = Query(sql); - var result = null as Result; - - if (query.Success) - { - try - { - var array = OrmHelper.Fill(query, model); - result = new Result(array); - } - catch (Exception ex) - { - result = new Result(ex); - } - } - else - { - result = new Result(query.Message); - } - - query.Dispose(); - return result; - } - - /// - public Result Query(string sql) where T : class, new() - { - var query = Query(sql); - if (!query.Success) return new Result(query.Message); - var records = query.Fill(); - query.Dispose(); - - var result = new Result(records); - return result; - } - - #endregion - - #region Static - - /// 对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。 - protected static string Escape(string text, int bytes = 0) - { - if (text.IsEmpty()) return ""; - - var t = text ?? ""; - t = t.Replace("\\", "\\\\"); - t = t.Replace("'", "\\'"); - t = t.Replace("\n", "\\n"); - t = t.Replace("\r", "\\r"); - t = t.Replace("\b", "\\b"); - t = t.Replace("\t", "\\t"); - t = t.Replace("\f", "\\f"); - - if (bytes > 5) - { - if (t.Bytes(Encoding.UTF8).Length > bytes) - { - while (true) - { - t = t.Substring(0, t.Length - 1); - if (t.Bytes(Encoding.UTF8).Length <= (bytes - 4)) break; - } - t = t + " ..."; - } - } - - return t; - } - - #endregion - - /// - protected abstract string GetConnectionString(); - - /// - protected abstract DbConnection NewConnection(); - - /// - protected abstract DbDataAdapter NewDataAdapter(string sql); - - /// - protected abstract DbCommand NewCommand(); - - } - -} diff --git a/Apewer/Source/IDbAdo.cs b/Apewer/Source/IDbAdo.cs new file mode 100644 index 0000000..1f2c55a --- /dev/null +++ b/Apewer/Source/IDbAdo.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; + +namespace Apewer.Source +{ + + /// 数据库客户端。 + public interface IDbAdo : IDisposable + { + + #region Connection + + /// 获取连接。 + IDbConnection Connection { get; } + + /// 数据库当前在线,表示连接可用。 + bool Online { get; } + + /// 连接数据库,若未连接则尝试连接,返回错误信息。 + string Connect(); + + #endregion + + #region ADO + + /// 查询。 + IQuery Query(string statement); + + /// 查询。 + IQuery Query(string statement, IEnumerable parameters); + + /// 执行。 + IExecute Execute(string statement); + + /// 执行。 + IExecute Execute(string statement, IEnumerable parameters); + + // /// 获取当前的事务对象。 + // IDbTransaction Transaction { get; } + + #endregion + + #region Transaction + + /// + /// 启动事务,可指定事务锁定行为。 + /// Chaos
无法覆盖隔离级别更高的事务中的挂起的更改。
+ /// ReadCommitted
在正在读取数据时保持共享锁,以避免脏读,但是在事务结束之前可以更改数据,从而导致不可重复的读取或幻像数据。
+ /// ReadUncommitted
可以进行脏读,意思是说,不发布共享锁,也不接受独占锁。
+ /// RepeatableRead
在查询中使用的所有数据上放置锁,以防止其他用户更新这些数据。 防止不可重复的读取,但是仍可以有幻像行。
+ /// Serializable
在 System.Data.DataSet 上放置范围锁,以防止在事务完成之前由其他用户更新行或向数据集中插入行。
+ /// Snapshot
通过在一个应用程序正在修改数据时存储另一个应用程序可以读取的相同数据版本来减少阻止。 表示您无法从一个事务中看到在其他事务中进行的更改,即便重新查询也是如此。
+ /// Unspecified = -1
正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。
+ ///
+ /// 在连接的生命周期结束时未结束事务,指定 TRUE 将自动提交,指定 FALSE 将自动回滚。 + /// 指定事务锁定行为,不指定时将使用默认值。 + string Begin(bool commit = false, Class isolation = null); + + /// 提交事务。 + /// 异常常见于事务已经提交或连接已断开。 + /// 提交失败时返回错误信息,成功时返回 NULL 值。 + string Commit(); + + /// 从挂起状态回滚事务。 + /// 异常常见于事务已经提交、已回滚或连接已断开。 + /// 提交失败时返回错误信息,成功时返回 NULL 值。 + string Rollback(); + + #endregion + + } + +} diff --git a/Apewer/Source/IDbClient.cs b/Apewer/Source/IDbClient.cs index 1734ed4..01fc084 100644 --- a/Apewer/Source/IDbClient.cs +++ b/Apewer/Source/IDbClient.cs @@ -6,7 +6,13 @@ using System.Data; namespace Apewer.Source { - /// 数据库引擎接口。 - public interface IDbClient : IDisposable, IDbClientBase, IDbClientAdo, IDbClientOrm { } + /// 数据库客户端。 + public interface IDbClient : IDisposable, IDbAdo, IDbOrm + { + + /// 获取或设置日志记录器。 + Logger Logger { get; set; } + + } } diff --git a/Apewer/Source/IDbClientAdo.cs b/Apewer/Source/IDbClientAdo.cs deleted file mode 100644 index 1fa4a40..0000000 --- a/Apewer/Source/IDbClientAdo.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Text; - -namespace Apewer.Source -{ - - /// 数据库访问接口。 - public interface IDbClientAdo : IDisposable - { - - #region Connection - - /// 获取连接。 - IDbConnection Connection { get; } - - /// 数据库当前在线,表示连接可用。 - bool Online { get; } - - /// 连接数据库,若未连接则尝试连接,返回错误信息。 - string Connect(); - - #endregion - - #region SQL - - /// 查询。 - IQuery Query(string statement); - - /// 查询。 - IQuery Query(string statement, IEnumerable parameters); - - /// 执行。 - IExecute Execute(string statement); - - /// 执行。 - IExecute Execute(string statement, IEnumerable parameters); - - // /// 获取当前的事务对象。 - // IDbTransaction Transaction { get; } - - #endregion - - #region Transaction - - // /// 启动事务。 - // /// 事务锁定:默认为快照方式,在完成提交前,其它连接无法获取当前事务挂起的更改。 - // /// 当关闭连接时,提交或回滚未处理的事务。 - // /// 当存在已经启动的事务时,无法再次启动(返回 NULL 值)。 - // string Begin(IsolationLevel isolation = IsolationLevel.Snapshot, bool commit = true); - - /// 启动事务。 - /// 当关闭连接时,提交或回滚未处理的事务。 - /// 当存在已经启动的事务时,无法再次启动(返回 NULL 值)。 - string Begin(bool commit = true); - - /// 提交事务。 - /// 异常常见于事务已经提交或连接已断开。 - /// 提交失败时返回错误信息,成功时返回 NULL 值。 - string Commit(); - - /// 从挂起状态回滚事务。 - /// 异常常见于事务已经提交、已回滚或连接已断开。 - /// 提交失败时返回错误信息,成功时返回 NULL 值。 - string Rollback(); - - #endregion - - } - -} diff --git a/Apewer/Source/IDbClientBase.cs b/Apewer/Source/IDbClientBase.cs deleted file mode 100644 index 2f15b4f..0000000 --- a/Apewer/Source/IDbClientBase.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Apewer.Source -{ - - /// 数据库引擎接口。 - public interface IDbClientBase : IDisposable - { - - /// 获取或设置日志记录器。 - Logger Logger { get; set; } - - } - -} diff --git a/Apewer/Source/IDbClientOrm.cs b/Apewer/Source/IDbOrm.cs similarity index 87% rename from Apewer/Source/IDbClientOrm.cs rename to Apewer/Source/IDbOrm.cs index 0e6e2be..ea25d91 100644 --- a/Apewer/Source/IDbClientOrm.cs +++ b/Apewer/Source/IDbOrm.cs @@ -1,14 +1,17 @@ using System; using System.Collections.Generic; +using System.Data; using System.Text; namespace Apewer.Source { - /// 数据库引擎支持 ORM 访问。 - public interface IDbClientOrm + /// 数据库客户端。 + public interface IDbOrm { + #region Orm + /// 初始化指定类型,以创建表或增加字段。 /// 要初始化的类型。 /// 错误信息。当成功时候返回空字符串。 @@ -53,11 +56,13 @@ namespace Apewer.Source /// 使用指定语句查询,获取查询结果。 /// 目标记录的类型。 /// 要执行的 SQL 语句。 - public Result Query(Type model, string sql); + /// 为 SQL 命令提供参数。 + public Result Query(Type model, string sql, IEnumerable parameters = null); /// 使用指定语句查询,获取查询结果。 /// 要执行的 SQL 语句。 - public Result Query(string sql) where T : class, new(); + /// 为 SQL 命令提供参数。 + public Result Query(string sql, IEnumerable parameters = null) where T : class, new(); /// 查询所有记录。 /// 目标记录的类型。 @@ -68,6 +73,8 @@ namespace Apewer.Source /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 public Result Query(long flag = 0) where T : class, IRecord, new(); + #endregion + } } diff --git a/Apewer/Source/IRecordMoment.cs b/Apewer/Source/IRecordMoment.cs index 3cde4eb..6fc07d7 100644 --- a/Apewer/Source/IRecordMoment.cs +++ b/Apewer/Source/IRecordMoment.cs @@ -14,6 +14,10 @@ namespace Apewer.Source /// 记录的更新时间,每次对此记录执行 UPDATE 时应更新此值为当前系统时间,字段长度不应超过 255 个字符。 string Updated { get; set; } + + /// 获取 Created 和 Updated 属性的值。 + string GenerateMoment(); + } } diff --git a/Apewer/Source/IRecordStamp.cs b/Apewer/Source/IRecordStamp.cs index da2b757..47cc933 100644 --- a/Apewer/Source/IRecordStamp.cs +++ b/Apewer/Source/IRecordStamp.cs @@ -15,6 +15,9 @@ namespace Apewer.Source /// 记录的更新时间,默认为本地时间。 long Updated { get; set; } + /// 获取 Created 和 Updated 属性的值。 + long GenerateStamp(); + } } diff --git a/Apewer/Source/OrmHelper.cs b/Apewer/Source/OrmHelper.cs index 51eec96..159cfb1 100644 --- a/Apewer/Source/OrmHelper.cs +++ b/Apewer/Source/OrmHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data; using System.Text; using static Apewer.NumberUtility; @@ -163,17 +164,17 @@ namespace Apewer.Source else if (pt.Equals(typeof(decimal))) setter.Invoke(record, new object[] { Decimal(value) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable((DateTime)value) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Byte(value) ) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(SByte(value) ) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Int16(value) ) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(UInt16(value) ) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Int32(value) ) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(UInt32(value) ) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Int64(value) ) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(UInt64(value) ) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Single(value) ) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Double(value) ) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Decimal(value) ) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Byte(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(SByte(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Int16(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(UInt16(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Int32(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(UInt32(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Int64(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(UInt64(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Single(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Double(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Decimal(value)) }); else { @@ -195,12 +196,13 @@ namespace Apewer.Source /// 数据库对象。 /// 记录模型。 /// SQL 语句。 - public static Result Query(IDbClientAdo database, Type model, string 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)) + using (var query = database.Query(sql, parameters)) { if (query == null) return new Result("查询实例无效。"); if (query.Table == null) @@ -230,14 +232,14 @@ namespace Apewer.Source /// 数据库对象。 /// 记录模型。 /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result Query(IDbClientAdo database, Type model, Func sqlGetter) + public static Result Query(IDbAdo database, Type model, Func sqlGetter) { if (sqlGetter == null) return new Result("SQL 语句获取函数无效。"); try { var tableName = TableStructure.Parse(model).Name; if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。"); - return Query(database, model, sqlGetter(tableName)); + return Query(database, model, sqlGetter(tableName), null); } catch (Exception ex) { @@ -249,14 +251,14 @@ namespace Apewer.Source /// 记录模型。 /// 数据库对象。 /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result Query(IDbClientAdo database, Func sqlGetter) where T : class, new() => As(Query(database, typeof(T), sqlGetter)); + public static Result Query(IDbAdo database, Func sqlGetter) where T : class, new() => As(Query(database, typeof(T), sqlGetter)); /// 获取具有指定主键的记录。 /// 数据库对象。 /// 记录模型。 /// 主键。 /// 生成 SQL 语句的函数,传入参数为表名和主键值。 - public static Result Get(IDbClientAdo database, Type model, string key, Func sqlGetter) + public static Result Get(IDbAdo database, Type model, string key, Func sqlGetter) { if (sqlGetter == null) return new Result("SQL 语句获取函数无效。"); @@ -292,13 +294,13 @@ namespace Apewer.Source /// 数据库对象。 /// 主键。 /// 生成 SQL 语句的函数,传入参数为表名和主键值。 - public static Result Get(IDbClientAdo database, string key, Func sqlGetter) where T : class, IRecord, new() => As(Get(database, typeof(T), key, sqlGetter)); + 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(IDbClientAdo database, Type model, Func sqlGetter) + public static Result Keys(IDbAdo database, Type model, Func sqlGetter) { if (database == null) return new Result("数据库无效。"); if (model == null) return new Result("模型类型无效。"); @@ -359,7 +361,7 @@ namespace Apewer.Source /// 记录模型。 /// 数据库对象。 /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result Keys(IDbClientAdo database, Func sqlGetter) where T : IRecord + public static Result Keys(IDbAdo database, Func sqlGetter) where T : IRecord { return Keys(database, typeof(T), sqlGetter); } @@ -378,17 +380,17 @@ namespace Apewer.Source if (string.IsNullOrEmpty(key.Key)) key.ResetKey(); } - var now = DateTime.Now; if (record is IRecordMoment moment) { - if (string.IsNullOrEmpty(moment.Created)) moment.Created = now.Lucid(); - if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now.Lucid(); + var now = moment.GenerateMoment(); + if (string.IsNullOrEmpty(moment.Created)) moment.Created = now; + if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now; } if (record is IRecordStamp stamp) { - var utc = ClockUtility.UtcStamp; - if (stamp.Created == 0L) stamp.Created = now.Stamp(); - if (stamp.Updated == 0L) stamp.Updated = now.Stamp(); + var now = stamp.GenerateStamp(); + if (stamp.Created == 0L) stamp.Created = now; + if (stamp.Updated == 0L) stamp.Updated = now; } } @@ -397,16 +399,15 @@ namespace Apewer.Source public static bool SetUpdated(object record) { if (record == null) return false; - var now = DateTime.Now; var setted = false; if (record is IRecordMoment moment) { - moment.Updated = now.Lucid(); + moment.Updated = moment.GenerateMoment(); setted = true; } if (record is IRecordStamp stamp) { - stamp.Updated = now.Stamp(); + stamp.Updated = stamp.GenerateStamp(); setted = true; } return setted; diff --git a/Apewer/Source/Query.cs b/Apewer/Source/Query.cs index aefd502..b8d489d 100644 --- a/Apewer/Source/Query.cs +++ b/Apewer/Source/Query.cs @@ -305,7 +305,7 @@ namespace Apewer.Source private static ObjectDisposedException DisposedException { get { return new ObjectDisposedException(typeof(Query).FullName); } } /// 简单查询:取结果中第 0 列所有单元格的文本形式,可指定查询后关闭服务器连接,返回结果中不包含无效文本。 - public static string[] SimpleColumn(IDbClientAdo database, string statement, bool dispose = false) + public static string[] SimpleColumn(IDbAdo database, string statement, bool dispose = false) { if (database == null) return new string[0]; var ab = new ArrayBuilder(); @@ -329,7 +329,7 @@ namespace Apewer.Source } /// 简单查询:取结果中第 0 行、第 0 列单元格中的文本,可指定查询后关闭服务器连接。 - public static string SimpleCell(IDbClientAdo database, string statement, bool dispose = false) + public static string SimpleCell(IDbAdo database, string statement, bool dispose = false) { if (database == null) return ""; var query = database.Query(statement); @@ -345,19 +345,12 @@ namespace Apewer.Source internal static string Text(object value) { - var result = Constant.EmptyString; - if (value != null) + if (value != null && !value.Equals(DBNull.Value)) { - if (!value.Equals(DBNull.Value)) - { - try - { - result = value.ToString(); - } - finally { } - } + if (value is string str) return str; + try { return value.ToString() ?? ""; } catch { } } - return result; + return ""; } internal static Class DateTime(object value) diff --git a/Apewer/Source/Timeout.cs b/Apewer/Source/Timeout.cs index badf2ea..3a22956 100644 --- a/Apewer/Source/Timeout.cs +++ b/Apewer/Source/Timeout.cs @@ -39,8 +39,8 @@ namespace Apewer.Source set { _execute = (value >= 0) ? value : 0; } } - /// 默认超时设置:连接 10000、查询 60000,执行 60000。 - public static Timeout Default { get => new Timeout(10000, 60000, 60000); } + /// 默认超时设置:连接 5000、查询 60000,执行 60000。 + public static Timeout Default { get => new Timeout(5000, 60000, 60000); } } diff --git a/Apewer/SystemUtility.cs b/Apewer/SystemUtility.cs deleted file mode 100644 index 2a92d78..0000000 --- a/Apewer/SystemUtility.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Apewer -{ - - /// 系统实用工具。 - public class SystemUtility - { - -#if NETSTD || NETCORE - - /// 当前操作系统是 Windows。 - public static bool IsWindows { get => System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows); } - - /// 当前操作系统是 OS X 或 macOS。 - public static bool IsOSX { get => System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX); } - - /// 当前操作系统是 Linux。 - public static bool IsLinux { get => System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux); } - -#endif - - /// - public static void SetConsoleCtrlCancel(Func exit) - { - const string postfix = " - 按 CTRL + C 可安全退出"; - var title = Console.Title; - if (!title.EndsWith(postfix)) - { - title = title + postfix; - Console.Title = title; - } - - if (exit == null) return; - Console.CancelKeyPress += (s, e) => - { - e.Cancel = !exit(); - }; - } - - } - -} diff --git a/Apewer/_Delegates.cs b/Apewer/_Delegates.cs index 2fad928..66d241e 100644 --- a/Apewer/_Delegates.cs +++ b/Apewer/_Delegates.cs @@ -22,11 +22,26 @@ namespace Apewer // public delegate TResult Func(T arg); /// - public delegate void Event(object sender, object argument); + public delegate void Event(object sender); /// public delegate void Event(object sender, T argument); + /// + public delegate void Event(object sender, T1 arg1, T2 arg2); + + /// + public delegate void Event(object sender, T1 arg1, T2 arg2, T3 arg3); + + /// + public delegate void Event(object sender, T1 arg1, T2 arg2, T3 arg3, T4 arg4); + + /// + public delegate void Event(object sender, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); + + /// + public delegate void Event(object sender, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); + /// 数组回调。 public delegate T ArrayCallback(object sender, params T[] arguments); diff --git a/Apewer/_Extensions.cs b/Apewer/_Extensions.cs index d5b025f..e870010 100644 --- a/Apewer/_Extensions.cs +++ b/Apewer/_Extensions.cs @@ -15,6 +15,12 @@ using System.Text; public static class Extensions_Apewer { + /// 是 NULL 值。 + public static bool IsNull(this object @this) => @this == null || @this.Equals(DBNull.Value); + + /// 不是 NULL 值。 + public static bool NotNull(this object @this) => @this != null && !@this.Equals(DBNull.Value); + #region Class Utility /// 克隆对象,创建新对象。可指定包含对象的属性和字段。 diff --git a/ChangeLog.md b/ChangeLog.md index e584b60..f507f94 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,14 @@  ### 最新提交 +### 6.5.6 +- Global:增加 IsNull 和 NotNull 扩展方法; +- ArrayBuilder:添加了 Add 重载方法; +- Network:新增 TcpProxy 类; +- RuntimeUtility:屏蔽 Console.Title 抛出的异常; +- Source:IRecordMoment 和 IRecordStamp 不再生成值,将由实现类提供; +- Source:修正 Query 方法中查询超时不生效的问题。 + ### 6.5.5 - Source:调整了 SqlClient 的查询超时设置。