From d23612141aaed253d874c6d90455b7ff565ee9f5 Mon Sep 17 00:00:00 2001 From: Elivo Date: Fri, 15 Oct 2021 11:20:00 +0800 Subject: [PATCH] Apewer-6.4.1 --- Apewer.Source/Apewer.Source.csproj | 12 + Apewer.Source/Internals/TextHelper.cs | 31 + Apewer.Source/Source/Access.cs | 274 ++++---- Apewer.Source/Source/MySql.cs | 402 ++++++----- .../Source/{SqlServer.cs => SqlClient.cs} | 643 ++++++++---------- Apewer.Source/Source/SqlServerSouce.cs | 26 + Apewer.Source/Source/Sqlite.cs | 420 ++++++------ Apewer.Web/Internals/ApiHelper.cs | 12 +- Apewer.Web/Web/ApiEntries.cs | 2 +- Apewer.Web/Web/ApiProcessor.cs | 8 + Apewer.Web/Web/ApiProgram.cs | 6 +- Apewer/ArrayBuilder.cs | 31 +- Apewer/ClockUtility.cs | 39 +- Apewer/Json.cs | 100 ++- Apewer/Network/HttpClient.cs | 2 +- Apewer/Result.cs | 16 +- Apewer/RuntimeUtility.cs | 45 +- Apewer/Source/ColumnAttribute.cs | 167 +++-- Apewer/Source/ColumnType.cs | 4 +- Apewer/Source/Example.cs | 26 +- Apewer/Source/Execute.cs | 78 +-- Apewer/Source/HttpRecord.cs | 14 +- Apewer/Source/IDatabaseBase.cs | 23 - Apewer/Source/IDatabaseExecute.cs | 21 - Apewer/Source/IDatabaseQuery.cs | 21 - Apewer/Source/{IDatabase.cs => IDbClient.cs} | 2 +- Apewer/Source/IDbClientAdo.cs | 72 ++ Apewer/Source/IDbClientBase.cs | 17 + .../{IDatabaseOrm.cs => IDbClientOrm.cs} | 14 +- Apewer/Source/IExecute.cs | 6 - Apewer/Source/IQuery.cs | 37 - Apewer/Source/IRecord.cs | 6 + Apewer/Source/OrmHelper.cs | 198 +++--- Apewer/Source/Parameter.cs | 20 +- Apewer/Source/Query.cs | 418 ++++-------- Apewer/Source/Record.cs | 20 +- Apewer/Source/TableAttribute.cs | 81 ++- Apewer/Source/TableStructure.cs | 408 ++++------- Apewer/Source/Timeout.cs | 4 +- Apewer/StringPairs.cs | 6 + Apewer/TextUtility.cs | 9 +- Apewer/Web/ApiOptions.cs | 4 + Apewer/Web/ApiUtility.cs | 6 +- Apewer/Web/DefaultController.cs | 20 + Apewer/_Common.props | 5 +- Apewer/_Extensions.cs | 51 +- ChangeLog.md | 10 + 47 files changed, 1943 insertions(+), 1894 deletions(-) create mode 100644 Apewer.Source/Internals/TextHelper.cs rename Apewer.Source/Source/{SqlServer.cs => SqlClient.cs} (63%) create mode 100644 Apewer.Source/Source/SqlServerSouce.cs delete mode 100644 Apewer/Source/IDatabaseBase.cs delete mode 100644 Apewer/Source/IDatabaseExecute.cs delete mode 100644 Apewer/Source/IDatabaseQuery.cs rename Apewer/Source/{IDatabase.cs => IDbClient.cs} (59%) create mode 100644 Apewer/Source/IDbClientAdo.cs create mode 100644 Apewer/Source/IDbClientBase.cs rename Apewer/Source/{IDatabaseOrm.cs => IDbClientOrm.cs} (87%) create mode 100644 Apewer/Web/DefaultController.cs diff --git a/Apewer.Source/Apewer.Source.csproj b/Apewer.Source/Apewer.Source.csproj index f6b622a..1c15c33 100644 --- a/Apewer.Source/Apewer.Source.csproj +++ b/Apewer.Source/Apewer.Source.csproj @@ -27,6 +27,18 @@ + + + + + + + + $(DefineConstants);MYSQL_6_10; + + + + diff --git a/Apewer.Source/Internals/TextHelper.cs b/Apewer.Source/Internals/TextHelper.cs new file mode 100644 index 0000000..61247c0 --- /dev/null +++ b/Apewer.Source/Internals/TextHelper.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Internals +{ + + internal static class TextHelper + { + + public static StringPairs ParseConnectionString(string connectionString) + { + var sp = new StringPairs(); + if (string.IsNullOrEmpty(connectionString)) return sp; + + var split = connectionString.Split(";"); + foreach (var item in split) + { + var equal = item.IndexOf("="); + if (equal < 0) continue; + var left = item.Substring(0, equal).ToTrim(); + var right = item.Substring(equal + 1).ToTrim(); + if (left.IsEmpty() || right.IsEmpty()) continue; + sp.Add(left, right); + } + return sp; + } + + } + +} diff --git a/Apewer.Source/Source/Access.cs b/Apewer.Source/Source/Access.cs index 6cee814..0863201 100644 --- a/Apewer.Source/Source/Access.cs +++ b/Apewer.Source/Source/Access.cs @@ -24,59 +24,34 @@ namespace Apewer.Source #if NETFRAMEWORK - public partial class Access : IDatabaseBase, IDatabaseQuery, IDatabaseExecute, IDisposable + public partial class Access : IDbClientBase, IDbClientAdo, IDisposable { - /// 创建 Access 类的新实例。 - public static Access Jet4() => new Access(AccessHelper.JetOleDB4); - - /// 创建 Access 类的新实例。 - public static Access Ace12() => new Access(AccessHelper.AceOleDB12); - - #region 属性、构造函数和 Dispose。 - - private OleDbConnection _connection = null; - - internal string Provider { get; set; } + #region 连接 - /// 构造函数。 - internal Access(string provider) - { - Provider = provider; - Timeout = new Timeout(); - } - - /// 释放资源。 - public void Dispose() => Close(); - - #endregion - - #region 日志。 + string _connstr = null; + OleDbConnection _connection = null; + Timeout _timeout = null; /// 获取或设置日志记录。 public Logger Logger { get; set; } - private void LogError(string action, Exception ex, string addtion) + /// 获取或设置超时。 + public Timeout Timeout { get => _timeout; } + + /// 构造函数。 + public Access(string connectrionString, Timeout timeout) { - var logger = Logger; - if (logger != null) logger.Error(this, "Access", action, ex.GetType().FullName, ex.Message, addtion); + _connstr = connectrionString; + _timeout = timeout ?? Timeout.Default; } #endregion - #region 连接。 + #region 连接 - /// 获取或设置数据库文件的路径。 - public string Path { get; set; } - - /// Microsoft Access System Database。 - public string Josd { get; set; } - - /// 获取或设置用于连接数据库的密码。 - public string Pass { get; set; } - - /// 获取或设置超时。 - public Timeout Timeout { get; set; } + /// 获取当前的 OldDbConnection 对象。 + public IDbConnection Connection { get => _connection; } /// 数据库是否已经连接。 public bool Online @@ -95,11 +70,10 @@ namespace Apewer.Source /// 是否已连接。 public bool Connect() { - var cs = GenerateConnectionString(); if (_connection == null) { _connection = new OleDbConnection(); - _connection.ConnectionString = cs; + _connection.ConnectionString = _connstr; } else { @@ -110,9 +84,9 @@ namespace Apewer.Source _connection.Open(); if (_connection.State == ConnectionState.Open) return true; } - catch (Exception argException) + catch (Exception ex) { - LogError("Connect", argException, cs); + Logger.Error(nameof(Access), "Connect", ex, _connstr); Close(); } return false; @@ -123,35 +97,91 @@ namespace Apewer.Source { if (_connection != null) { + if (_transaction != null) + { + if (_autocommit) Commit(); + else Rollback(); + } _connection.Close(); _connection.Dispose(); _connection = null; } } - /// 获取或设置连接字符串。 - private string GenerateConnectionString() - { - if (!File.Exists(Path)) return null; + /// 释放资源。 + public void Dispose() => Close(); - var sb = new StringBuilder(); + #endregion - sb.Append("provider=", Provider, "; "); + #region Transaction - if (!string.IsNullOrEmpty(Path)) sb.Append("data source=", Path, "; "); + private IDbTransaction _transaction = null; + private bool _autocommit = false; - if (string.IsNullOrEmpty(Pass)) sb.Append("persist security info=false; "); - else sb.Append("jet oledb:database password=\"", Pass, "\"; "); + /// 启动事务。 + public string Begin(bool commit = true) => Begin(commit, null); - // Microsoft Access Workgroup Information - if (!string.IsNullOrEmpty(Josd)) sb.Append("jet oledb:system database=", Josd, "; "); + /// 启动事务。 + public string Begin(bool commit, Class isolation) + { + if (!Connect()) return "未连接。"; + if (_transaction != null) return "存在已启动的事务,无法再次启动。"; + try + { + _transaction = isolation ? _connection.BeginTransaction(isolation.Value) : _connection.BeginTransaction(); + _autocommit = commit; + return null; + } + catch (Exception ex) + { + Logger.Error(nameof(Access), "Begin", ex.Message()); + return ex.Message(); + } + } - return sb.ToString(); + /// 提交事务。 + 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(nameof(Access), "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(nameof(Access), "Rollback", ex.Message); + return ex.Message(); + } } #endregion - #region 查询和执行。 + #region 查询和执行 /// 使用 SQL 语句进行查询。 public IQuery Query(string sql) => Query(sql, null); @@ -161,43 +191,40 @@ namespace Apewer.Source { if (sql.IsBlank()) return Example.InvalidQueryStatement; - const string table = "queryresult"; - var connected = Connect(); if (!connected) return Example.InvalidQueryConnection; - var query = new Query(); try { - var command = new OleDbCommand(); - command.Connection = _connection; - command.CommandTimeout = Timeout.Query; - command.CommandText = sql; - if (parameters != null) + using (var command = new OleDbCommand()) { - foreach (var p in parameters) + command.Connection = _connection; + command.CommandTimeout = Timeout.Query; + command.CommandText = sql; + if (parameters != null) { - if (p != null) command.Parameters.Add(p); + foreach (var p in parameters) + { + if (p != null) command.Parameters.Add(p); + } } - } - using (var ds = new DataSet()) - { - using (var da = new OleDbDataAdapter(sql, _connection)) + using (var ds = new DataSet()) { - da.Fill(ds, table); - query.Table = ds.Tables[table]; + using (var da = new OleDbDataAdapter(sql, _connection)) + { + const string name = "result"; + da.Fill(ds, name); + var table = ds.Tables[name]; + return new Query(table); + } } } - command.Dispose(); - query.Success = true; } catch (Exception exception) { - LogError("Query", exception, sql); - query.Success = false; - query.Exception = exception; + Logger.Error(nameof(Access), "Query", exception, sql); + return new Query(exception); } - return query; } /// 执行 SQL 语句。 @@ -211,40 +238,35 @@ namespace Apewer.Source var connected = Connect(); if (!connected) return Example.InvalidExecuteConnection; - var execute = new Execute(); - using (var transaction = _connection.BeginTransaction()) + var inTransaction = _transaction != null; + if (!inTransaction) Begin(); + try { - try + using (var command = new OleDbCommand()) { - using (var command = new OleDbCommand()) + command.Connection = _connection; + command.Transaction = (OleDbTransaction)_transaction; + command.CommandTimeout = Timeout.Execute; + command.CommandText = sql; + if (parameters != null) { - command.Connection = _connection; - command.Transaction = transaction; - command.CommandTimeout = Timeout.Execute; - command.CommandText = sql; - if (parameters != null) + foreach (var parameter in parameters) { - foreach (var parameter in parameters) - { - if (parameter == null) continue; - command.Parameters.Add(parameter); - } + if (parameter == null) continue; + command.Parameters.Add(parameter); } - execute.Rows += command.ExecuteNonQuery(); - transaction.Commit(); } - execute.Success = true; - } - catch (Exception exception) - { - LogError("Execute", exception, sql); - try { transaction.Rollback(); } catch { } - execute.Success = false; - execute.Exception = exception; + var rows = command.ExecuteNonQuery(); + if (!inTransaction) Commit(); // todo 此处应该检查事务提交产生的错误。 + return new Execute(true, rows); } } - - return execute; + catch (Exception exception) + { + Logger.Error(nameof(Access), "Execute", exception, sql); + if (!inTransaction) Rollback(); + return new Execute(exception); + } } #endregion @@ -280,20 +302,41 @@ namespace Apewer.Source #endregion + #region protected + + /// 获取或设置连接字符串。 + internal protected static string GenerateCS(string provider, string path, string pass, string jo) + { + if (!File.Exists(path)) return null; + + var sb = new StringBuilder(); + + sb.Append("provider=", provider, "; "); + + if (!string.IsNullOrEmpty(path)) sb.Append("data source=", path, "; "); + + if (string.IsNullOrEmpty(pass)) sb.Append("persist security info=false; "); + else sb.Append("jet oledb:database password=\"", pass, "\"; "); + + // Microsoft Access Workgroup Information + if (!string.IsNullOrEmpty(jo)) sb.Append("jet oledb:system database=", jo, "; "); + + return sb.ToString(); + } + + #endregion + } /// 使用 Microsoft.Jet.OLEDB.4.0 访问 Access 97 - 2003 数据库文件。 public class AccessJet4 : Access { - /// 创建 Access 类的新实例。 - public AccessJet4() : base(AccessHelper.JetOleDB4) { } + const string JetOleDB4 = "microsoft.jet.oledb.4.0"; /// 创建 Access 类的新实例。 - public AccessJet4(string path) : base(AccessHelper.JetOleDB4) - { - Path = path; - } + public AccessJet4(string path, string pass = null, string jo = null, Timeout timeout = null) + : base(GenerateCS(JetOleDB4, path, pass, jo), timeout) { } } @@ -301,14 +344,11 @@ namespace Apewer.Source public class AccessAce12 : Access { - /// 创建 Access 类的新实例。 - public AccessAce12() : base(AccessHelper.AceOleDB12) { } + const string AceOleDB12 = "microsoft.ace.oledb.12.0"; /// 创建 Access 类的新实例。 - public AccessAce12(string path) : base(AccessHelper.AceOleDB12) - { - Path = path; - } + 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/MySql.cs b/Apewer.Source/Source/MySql.cs index ee1a3ff..35469e5 100644 --- a/Apewer.Source/Source/MySql.cs +++ b/Apewer.Source/Source/MySql.cs @@ -1,75 +1,61 @@ #if MYSQL_6_9 || MYSQL_6_10 -/* 2021.09.23 */ +/* 2021.10.14 */ using Externals.MySql.Data.MySqlClient; using System; using System.Collections.Generic; using System.Data; +using System.Net; using System.Text; +using System.Transactions; namespace Apewer.Source { /// - public class MySql : IDatabase + public class MySql : IDbClient { - #region fields & properties + #region 基础 - private const string EmptyString = TextUtility.Empty; + private Timeout _timeout = null; + private string _connectionstring = null; - private MySqlConnection _connection = null; - private Timeout _timeout = new Timeout(); - private string _address = EmptyString; - private string _store = EmptyString; - private string _user = "root"; - private string _pass = EmptyString; - - /// - public string Address { get { return _address; } set { _address = TextUtility.AntiInject(value); } } - - /// - public string Store { get { return _store; } set { _store = TextUtility.AntiInject(value); } } - - /// - public string User { get { return _user; } set { _user = TextUtility.AntiInject(value); } } - - /// - public string Pass { get { return _pass; } set { _pass = TextUtility.AntiInject(value); } } + /// 获取或设置日志记录。 + public Logger Logger { get; set; } - /// - public Timeout Timeout { get { return _timeout; } set { _timeout = value; } } + /// 超时设定。 + public Timeout Timeout { get => _timeout; } - /// - public bool Online + /// 创建实例。 + public MySql(string connnectionString, Timeout timeout = default) { - get - { - if (_connection == null) return false; - return _connection.State == ConnectionState.Open; - } + _connectionstring = connnectionString; + _timeout = timeout ?? Timeout.Default; } - /// - public MySql() { } + /// 获取当前的 MySqlConnection 对象。 + public IDbConnection Connection { get => _connection; } - /// - public MySql(string address, string store, string user, string pass = null) + /// 构建连接字符串以创建实例。 + public MySql(string address, string store, string user, string pass, Timeout timeout = null) { - Address = address; - Store = store; - User = user; - Pass = pass; + _timeout = timeout ?? Timeout.Default; + + var a = TextUtility.AntiInject(address); + var s = TextUtility.AntiInject(store); + var u = TextUtility.AntiInject(user); + var p = TextUtility.AntiInject(pass); + var cs = $"server={a}; database={s}; uid={u}; pwd={p}; "; + _connectionstring = cs; + _storename = new Class(s); } #endregion #region 日志。 - /// 获取或设置日志记录。 - public Logger Logger { get; set; } - private void LogError(string action, Exception ex, string addtion) { var logger = Logger; @@ -78,12 +64,15 @@ namespace Apewer.Source #endregion - #region methods + #region Connection - private string CombineString() - { - return TextUtility.Merge("server=", _address, "; database=", _store, "; uid=", _user, "; pwd=", _pass, ";"); - } + private MySqlConnection _connection = null; + + /// + public bool Online { get => _connection == null ? false : (_connection.State == ConnectionState.Open); } + + /// 连接字符串。 + public string ConnectionString { get => _connectionstring; } /// public bool Connect() @@ -91,7 +80,7 @@ namespace Apewer.Source if (_connection == null) { _connection = new MySqlConnection(); - _connection.ConnectionString = CombineString(); + _connection.ConnectionString = _connectionstring; } else { @@ -120,6 +109,11 @@ namespace Apewer.Source { if (_connection != null) { + if (_transaction != null) + { + if (_autocommit) Commit(); + else Rollback(); + } _connection.Close(); _connection.Dispose(); _connection = null; @@ -129,48 +123,117 @@ namespace Apewer.Source /// public void Dispose() { Close(); } + #endregion + + #region Transaction + + private IDbTransaction _transaction = null; + private bool _autocommit = false; + + /// 启动事务。 + public string Begin(bool commit = true) => Begin(commit, null); + + /// 启动事务。 + public string Begin(bool commit, Class isolation) + { + if (!Connect()) return "未连接。"; + if (_transaction != null) return "存在已启动的事务,无法再次启动。"; + try + { + _transaction = isolation ? _connection.BeginTransaction(isolation.Value) : _connection.BeginTransaction(); + _autocommit = commit; + return null; + } + catch (Exception ex) + { + Logger.Error(nameof(MySql), "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(nameof(MySql), "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(nameof(MySql), "Rollback", ex.Message()); + return ex.Message(); + } + } + + #endregion + + #region SQL + /// public IQuery Query(string sql, IEnumerable parameters) { if (sql.IsBlank()) return Example.InvalidQueryStatement; - const string table = "queryresult"; - var connected = Connect(); if (!connected) return Example.InvalidQueryConnection; - var query = new Query(); try { - var command = new MySqlCommand(); - command.Connection = _connection; - command.CommandTimeout = _timeout.Query; - command.CommandText = sql; - if (parameters != null) + using (var command = new MySqlCommand()) { - foreach (var p in parameters) + command.Connection = _connection; + command.CommandTimeout = _timeout.Query; + command.CommandText = sql; + if (parameters != null) { - if (p != null) command.Parameters.Add(p); + foreach (var p in parameters) + { + if (p != null) command.Parameters.Add(p); + } } - } - using (var ds = new DataSet()) - { - using (var da = new MySqlDataAdapter(sql, _connection)) + using (var ds = new DataSet()) { - da.Fill(ds, table); - query.Table = ds.Tables[table]; + using (var da = new MySqlDataAdapter(sql, _connection)) + { + const string name = "result"; + da.Fill(ds, name); + var table = ds.Tables[name]; + return new Query(table); + } } } - command.Dispose(); - query.Success = true; } catch (Exception exception) { - LogError("Query", exception, sql); - query.Success = false; - query.Exception = exception; + Logger.Error(nameof(MySql), "Query", exception, sql); + return new Query(exception); } - return query; } /// @@ -181,37 +244,35 @@ namespace Apewer.Source var connected = Connect(); if (!connected) return Example.InvalidExecuteConnection; - var transaction = _connection.BeginTransaction(); - var execute = new Execute(); + var inTransaction = _transaction != null; + if (!inTransaction) Begin(); try { - var command = new MySqlCommand(); - command.Connection = _connection; - command.Transaction = transaction; - command.CommandTimeout = _timeout.Execute; - command.CommandText = sql; - if (parameters != null) + using (var command = new MySqlCommand()) { - foreach (var parameter in parameters) + command.Connection = _connection; + command.Transaction = (MySqlTransaction)_transaction; + command.CommandTimeout = _timeout.Execute; + command.CommandText = sql; + if (parameters != null) { - if (parameter == null) continue; - command.Parameters.Add(parameter); + foreach (var parameter in parameters) + { + if (parameter == null) continue; + command.Parameters.Add(parameter); + } } + var rows = command.ExecuteNonQuery(); + if (!inTransaction) Commit(); // todo 此处应该检查事务提交产生的错误。 + return new Execute(true, rows); } - execute.Rows += command.ExecuteNonQuery(); - transaction.Commit(); - command.Dispose(); - execute.Success = true; } catch (Exception exception) { - LogError("Execute", exception, sql); - try { transaction.Rollback(); } catch { } - execute.Success = false; - execute.Exception = exception; + Logger.Error(nameof(MySql), "Execute", exception, sql); + if (!inTransaction) Rollback(); + return new Execute(exception); } - try { transaction.Dispose(); } catch { } - return execute; } /// @@ -241,29 +302,38 @@ namespace Apewer.Source #region ORM - private List FirstColumn(string sql) + private Class _storename = null; + + private string StoreName() + { + if (_storename) return _storename.Value; + _storename = new Class(Internals.TextHelper.ParseConnectionString(_connectionstring).GetValue("database")); + return _storename.Value ?? ""; + } + + private string[] FirstColumn(string sql) { using (var query = Query(sql) as Query) return query.ReadColumn(); } /// - public List TableNames() + public string[] TableNames() { - var sql = TextUtility.Merge("select table_name from information_schema.tables where table_schema='", _store, "' and table_type='base table';"); + var sql = TextUtility.Merge("select table_name from information_schema.tables where table_schema='", StoreName(), "' and table_type='base table';"); return FirstColumn(sql); } /// - public List ViewNames() + public string[] ViewNames() { - var sql = TextUtility.Merge("select table_name from information_schema.tables where table_schema='", _store, "' and table_type='view';"); + var sql = TextUtility.Merge("select table_name from information_schema.tables where table_schema='", StoreName(), "' and table_type='view';"); return FirstColumn(sql); } /// - public List ColumnNames(string table) + public string[] ColumnNames(string table) { - var sql = TextUtility.Merge("select column_name from information_schema.columns where table_schema='", _store, "' and table_name='", TextUtility.AntiInject(table), "';"); + var sql = TextUtility.Merge("select column_name from information_schema.columns where table_schema='", StoreName(), "' and table_name='", TextUtility.AntiInject(table), "';"); return FirstColumn(sql); } @@ -273,9 +343,9 @@ namespace Apewer.Source // 检查现存表。 var exists = false; var tables = TableNames(); - if (tables.Count > 0) + if (tables.Length > 0) { - var lower = structure.Table.ToLower(); + var lower = structure.Name.ToLower(); foreach (var table in tables) { if (TextUtility.IsBlank(table)) continue; @@ -289,10 +359,10 @@ namespace Apewer.Source if (exists) { - var columns = ColumnNames(structure.Table); - if (columns.Count > 0) + var columns = ColumnNames(structure.Name); + if (columns.Length > 0) { - var lower = new List(columns.Count); + var lower = new List(columns.Length); var added = 0; foreach (var column in columns) { @@ -301,10 +371,10 @@ namespace Apewer.Source added++; } lower.Capacity = added; - columns = lower; + columns = lower.ToArray(); } var sqlsb = new StringBuilder(); - foreach (var column in structure.Columns.Values) + foreach (var column in structure.Columns) { // 检查 Independent 特性。 if (structure.Independent && column.Independent) continue; @@ -317,7 +387,7 @@ namespace Apewer.Source if (type.IsEmpty()) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); // alter table `_record` add column `_index` bigint; - sqlsb.Append("alter table `", structure.Table, "` add column ", type, "; "); + sqlsb.Append("alter table `", structure.Name, "` add column ", type, "; "); } var sql = sqlsb.ToString(); return sql; @@ -326,14 +396,11 @@ namespace Apewer.Source { // create table _record (`_index` bigint, `_key` varchar(255), `_text` longtext) engine=innodb default charset=utf8mb4 - var columns = new List(structure.Columns.Count); + var columns = new List(structure.Columns.Length); var columnsAdded = 0; var primarykey = null as string; - foreach (var kvp in structure.Columns) + foreach (var column in structure.Columns) { - var property = kvp.Key; - var column = kvp.Value; - // 检查 Independent 特性。 if (structure.Independent && column.Independent) continue; @@ -344,10 +411,10 @@ namespace Apewer.Source columnsAdded++; // 主键。 - if (property == "Key") primarykey = column.Field; + if (column.Property.Name == "Key") primarykey = column.Field; } columns.Capacity = columnsAdded; - var table = structure.Table; + var table = structure.Name; var joined = string.Join(", ", columns); // 设置主键。 @@ -370,22 +437,21 @@ namespace Apewer.Source { if (model == null) { - sql = ""; + sql = null; return "指定的类型无效。"; } - var structure = null as TableStructure; - try { structure = TableStructure.ParseModel(model); } - catch (Exception exception) + var structure = TableStructure.Parse(model); + if (structure == null) { - sql = ""; - return exception.Message; + sql = null; + return "无法解析记录模型。"; } // 连接数据库。 if (!Connect()) { - sql = ""; + sql = null; return "连接数据库失败。"; } @@ -393,7 +459,7 @@ namespace Apewer.Source if (sql.NotEmpty()) { var execute = Execute(sql); - if (!execute.Success) return execute.Error; + if (!execute.Success) return execute.Message; } return null; } @@ -407,62 +473,56 @@ namespace Apewer.Source /// public string Initialize(Record model) => (model == null) ? "参数无效。" : Initialize(model.GetType()); - /// 插入记录。成功时候返回空字符串,发生异常时返回异常信息。 + /// 插入记录。返回错误信息。 public string Insert(IRecord record) { if (record == null) return "参数无效。"; record.FixProperties(); - var structure = null as TableStructure; - try { structure = TableStructure.ParseModel(record); } - catch (Exception exception) { return exception.Message; } + var structure = TableStructure.Parse(record.GetType()); + if (structure == null) return "无法解析记录模型。"; - var parameters = structure.CreateDataParameters(record, CreateDataParameter); - var sql = GenerateInsertStatement(structure.Table, parameters); + var parameters = structure.CreateParameters(record, CreateDataParameter); + var sql = GenerateInsertStatement(structure.Name, parameters); var execute = Execute(sql, parameters); if (execute.Success) return TextUtility.Empty; - return execute.Error; + return execute.Message; } - /// - /// 更新记录,实体中的 Created 和 Key 属性不被更新。成功时返回空字符串,发生异常时返回异常信息。 - /// 无法更新拥有 Independent 特性的模型。 - /// + /// 更新记录,实体中的 Key 属性不被更新。返回错误信息。 + /// 无法更新带有 Independent 特性的模型(缺少 Key 属性)。 public string Update(IRecord record) { if (record == null) return "参数无效。"; record.FixProperties(); record.SetUpdated(); - var structure = null as TableStructure; - try { structure = TableStructure.ParseModel(record); } - catch (Exception exception) { return exception.Message; } - - // 检查 Independent 特性。 - if (structure.Independent) return "无法更新拥有 Independent 特性的模型。"; + var structure = TableStructure.Parse(record.GetType()); + if (structure == null) return "无法解析记录模型。"; + if (structure.Independent) return "无法更新带有 Independent 特性的模型。"; - var parameters = structure.CreateDataParameters(record, CreateDataParameter, "_created", "_key"); + var parameters = structure.CreateParameters(record, CreateDataParameter, "_key"); var sql = GenerateUpdateStatement(structure, record.Key, parameters); var execute = Execute(sql, parameters); if (execute.Success) return TextUtility.Empty; - return execute.Error; + return execute.Message; } /// - public Result> Query(Type model, string sql) => OrmHelper.Query(this, model, sql); + public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); /// - public Result> Query(string sql) where T : class, IRecord, new() => OrmHelper.Query(this, sql); + public Result Query(string sql) where T : class, IRecord, new() => OrmHelper.Query(this, sql); /// 获取所有记录。Flag 为 0 时将忽略 Flag 条件。 - public Result> Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => + public 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}; "; }); /// 获取所有记录。Flag 为 0 时将忽略 Flag 条件。 - public Result> Query(long flag = 0) where T : class, IRecord, new() => OrmHelper.Query(this, (tn) => + public Result Query(long flag = 0) where T : class, IRecord, new() => OrmHelper.Query(this, (tn) => { if (flag == 0) return $"select * from `{tn}`; "; return $"select * from `{tn}` where `_flag`={flag}; "; @@ -472,22 +532,20 @@ namespace Apewer.Source /// 填充的记录模型。 /// 要跳过的记录数,可用最小值为 0。 /// 要获取的记录数,可用最小值为 1。 - /// - public Result> Query(Type model, int skip, int count) + public Result Query(Type model, int skip, int count) { - if (skip < 0) return new Result>(new ArgumentOutOfRangeException(nameof(skip))); - if (count < 1) return new Result>(new ArgumentOutOfRangeException(nameof(count))); + if (skip < 0) return new Result("参数 skip 超出了范围。"); + if (count < 1) return new Result("参数 count 超出了范围。"); return OrmHelper.Query(this, model, (tn) => $"select * from `{tn}` limit {skip}, {count}; "); } /// 获取记录。 /// 要跳过的记录数,可用最小值为 0。 /// 要获取的记录数,可用最小值为 1。 - /// - public Result> Query(int skip, int count) where T : class, IRecord, new() + public Result Query(int skip, int count) where T : class, IRecord, new() { - if (skip < 0) return new Result>(new ArgumentOutOfRangeException(nameof(skip))); - if (count < 1) return new Result>(new ArgumentOutOfRangeException(nameof(count))); + if (skip < 0) return new Result("参数 skip 超出了范围。"); + if (count < 1) return new Result("参数 count 超出了范围。"); return OrmHelper.Query(this, (tn) => $"select * from `{tn}` limit {skip}, {count}; "); } @@ -506,14 +564,14 @@ namespace Apewer.Source }); /// >获取指定类型的主键,按 Flag 属性筛选。 - public Result> Keys(Type model, long flag = 0) => OrmHelper.Keys(this, model, (tn) => + public 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};"; }); /// >获取指定类型的主键,按 Flag 属性筛选。 - public Result> Keys(long flag = 0) where T : class, IRecord, new() => Keys(typeof(T), flag); + public Result Keys(long flag = 0) where T : class, IRecord, new() => Keys(typeof(T), flag); /// 对表添加列,返回错误信息。 /// 记录类型。 @@ -521,12 +579,14 @@ namespace Apewer.Source /// 字段类型。 /// 字段长度,仅对 VarChar 和 NVarChar 类型有效。 /// - public string AddColumn(string column, ColumnType type, int length = 0) where T : Record + public string AddColumn(string column, ColumnType type, int length = 0) where T : class, IRecord { var columnName = SafeColumn(column); if (columnName.IsEmpty()) return "列名无效。"; - var tableName = TableStructure.ParseTable(typeof(T)).Name; + var ta = TableAttribute.Parse(typeof(T)); + if (ta == null) return "无法解析记录模型。"; + var tableName = ta.Name; var columeType = ""; switch (type) @@ -547,9 +607,9 @@ namespace Apewer.Source case ColumnType.NVarChar: columeType = $"varchar({length})"; break; - case ColumnType.VarChar255: - case ColumnType.NVarChar255: - columeType = "varchar(255)"; + case ColumnType.VarChar191: + case ColumnType.NVarChar191: + columeType = "varchar(191)"; break; case ColumnType.VarCharMax: case ColumnType.NVarCharMax: @@ -563,7 +623,7 @@ namespace Apewer.Source var sql = $"alter table `{tableName}` add {columnName} {columeType}; "; var execute = Execute(sql) as Execute; - var error = execute.Error; + var error = execute.Message; return error; } @@ -650,10 +710,10 @@ namespace Apewer.Source dbtype = MySqlDbType.DateTime; break; case ColumnType.VarChar: - case ColumnType.VarChar255: + case ColumnType.VarChar191: case ColumnType.VarCharMax: case ColumnType.NVarChar: - case ColumnType.NVarChar255: + case ColumnType.NVarChar191: case ColumnType.NVarCharMax: dbtype = MySqlDbType.VarChar; break; @@ -671,9 +731,9 @@ namespace Apewer.Source case ColumnType.NVarChar: size = NumberUtility.Restrict(size, 0, 65535); break; - case ColumnType.VarChar255: - case ColumnType.NVarChar255: - size = NumberUtility.Restrict(size, 0, 255); + case ColumnType.VarChar191: + case ColumnType.NVarChar191: + size = NumberUtility.Restrict(size, 0, 191); break; default: size = 0; @@ -725,8 +785,8 @@ namespace Apewer.Source case ColumnType.VarChar: type = TextUtility.Merge("varchar(", Math.Max(65535, length).ToString(), ")"); break; - case ColumnType.VarChar255: - type = TextUtility.Merge("varchar(255)"); + case ColumnType.VarChar191: + type = TextUtility.Merge("varchar(191)"); break; case ColumnType.VarCharMax: type = TextUtility.Merge("varchar(max)"); @@ -737,8 +797,8 @@ namespace Apewer.Source case ColumnType.NVarChar: type = TextUtility.Merge("varchar(", Math.Min(65535, length).ToString(), ")"); break; - case ColumnType.NVarChar255: - type = TextUtility.Merge("varchar(255)"); + case ColumnType.NVarChar191: + type = TextUtility.Merge("varchar(191)"); break; case ColumnType.NVarCharMax: type = TextUtility.Merge("varchar(65535)"); @@ -844,7 +904,7 @@ namespace Apewer.Source { var result = TextUtility.Empty; - var table = TextUtility.AntiInject(structure.Table, 255); + var table = TextUtility.AntiInject(structure.Name, 255); if (TextUtility.IsEmpty(table)) return result; var safekey = TextUtility.AntiInject(key, 255); @@ -885,7 +945,7 @@ namespace Apewer.Source if (structure == null) throw new ArgumentNullException("structure"); if (key == null) throw new ArgumentNullException("key"); - var table = TextUtility.AntiInject(structure.Table, 255); + var table = TextUtility.AntiInject(structure.Name, 255); if (TextUtility.IsBlank(table)) throw new ArgumentException("表名无效。", "structure"); var safekey = TextUtility.AntiInject(key, 255); diff --git a/Apewer.Source/Source/SqlServer.cs b/Apewer.Source/Source/SqlClient.cs similarity index 63% rename from Apewer.Source/Source/SqlServer.cs rename to Apewer.Source/Source/SqlClient.cs index 0e5eb94..6948af9 100644 --- a/Apewer.Source/Source/SqlServer.cs +++ b/Apewer.Source/Source/SqlClient.cs @@ -1,6 +1,4 @@ -#if NETFRAMEWORK - -/* 2021.09.23 */ +/* 2021.10.14 */ using Apewer; using Apewer.Source; @@ -8,88 +6,74 @@ using System; using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Data.Sql; using System.Data.SqlClient; +using System.Net; using System.Text; +#if NETFRAMEWORK +using System.Data.Sql; +#else +#endif + namespace Apewer.Source { - /// 用于快速连接 Microsoft SQL Server 数据库的辅助。 + /// [Serializable] - public class SqlClinet : IDatabase + public class SqlClient : IDbClient { - #region 变量定义。 - - private SqlConnection _db = null; + #region 变量、构造函数 - private Timeout _timeout; + private Timeout _timeout = null; private string _connectionstring = ""; - private string _address = ""; - private string _store = ""; - private string _user = ""; - private string _pass = ""; - #endregion + /// 获取或设置日志记录。 + public Logger Logger { get; set; } - #region 构造函数。 - - /// 创建空参数的数据库连接实例。 - public SqlClinet() - { - _timeout = Timeout.Default; - } + /// 超时设定。 + public Timeout Timeout { get => _timeout; } /// 使用连接字符串创建数据库连接实例。 - public SqlClinet(string connectionString) + public SqlClient(string connectionString, Timeout timeout = null) { - _timeout = Timeout.Default; + _timeout = timeout ?? Timeout.Default; _connectionstring = connectionString ?? ""; } /// 使用连接凭据创建数据库连接实例。 - /// 服务器地址。 - /// 数据库名称。 - public SqlClinet(string address, string store) - { - _timeout = Timeout.Default; - _address = address ?? ""; - _store = store ?? ""; - UpdateConnectString(); - } + public SqlClient(string address, string store, string user, string pass, Timeout timeout = null) + { + if (timeout == null) timeout = Timeout.Default; - /// 使用连接凭据创建数据库连接实例。 - /// 服务器地址。 - /// 数据库名称。 - /// 用户名。 - /// 密码。 - public SqlClinet(string address, string store, string user, string pass) - { - _timeout = Timeout.Default; - _address = address ?? ""; - _store = store ?? ""; - _user = user ?? ""; - _pass = pass ?? ""; - UpdateConnectString(); + var a = TextUtility.AntiInject(address); + var s = TextUtility.AntiInject(store); + var u = TextUtility.AntiInject(user); + var p = TextUtility.AntiInject(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}; "; + + _timeout = timeout ?? Timeout.Default; + _connectionstring = cs; } #endregion - #region 日志。 - - /// 获取或设置日志记录。 - public Logger Logger { get; set; } + #region Ado - Connection - private void LogError(string action, Exception ex, string addtion) - { - var logger = Logger; - if (logger != null) logger.Error(this, "SQL Server", action, ex.GetType().FullName, ex.Message, addtion); - } + private SqlConnection _db = null; - #endregion + /// 连接字符串。 + public string ConnectionString { get => _connectionstring; } - #region 实现接口。 + /// 获取当前的 SqlConnection 对象。 + public IDbConnection Connection { get => _db; } /// 数据库是否已经连接。 public bool Online @@ -107,7 +91,7 @@ namespace Apewer.Source if (_db == null) { _db = new SqlConnection(); - _db.ConnectionString = ConnectionString; + _db.ConnectionString = _connectionstring; } else { @@ -124,7 +108,7 @@ namespace Apewer.Source } catch (Exception ex) { - LogError("Connection", ex, _db.ConnectionString); + Logger.Error(nameof(SqlClient), "Connection", ex, _db.ConnectionString); Close(); return false; } @@ -135,6 +119,11 @@ namespace Apewer.Source { if (_db != null) { + if (_transaction != null) + { + if (_autocommit) Commit(); + else Rollback(); + } _db.Close(); _db.Dispose(); _db = null; @@ -142,16 +131,80 @@ namespace Apewer.Source } /// 关闭连接,释放对象所占用的系统资源,并清除连接信息。 - public void Dispose() - { - Close(); - _connectionstring = ""; - _address = ""; - _store = ""; - _user = ""; - _pass = ""; + public void Dispose() => Close(); + + #endregion + + #region Ado - Transaction + + private IDbTransaction _transaction = null; + private bool _autocommit = false; + + /// 启动事务。 + public string Begin(bool commit = true) => Begin(commit, null); + + /// 启动事务。 + public string Begin(bool commit, Class isolation) + { + if (!Connect()) return "未连接。"; + if (_transaction != null) return "存在已启动的事务,无法再次启动。"; + try + { + _transaction = isolation ? _db.BeginTransaction(isolation.Value) : _db.BeginTransaction(); + _autocommit = commit; + return null; + } + catch (Exception ex) + { + Logger.Error(nameof(SqlClient), "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(nameof(SqlClient), "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(nameof(SqlClient), "Rollback", ex.Message); + return ex.Message(); + } } + #endregion + + #region Ado - SQL + /// 查询。 public IQuery Query(string sql) => Query(sql, null); @@ -159,44 +212,40 @@ namespace Apewer.Source public IQuery Query(string sql, IEnumerable parameters) { if (TextUtility.IsBlank(sql)) return Example.InvalidQueryStatement; - - const string tablename = "queryresult"; - var connected = Connect(); if (!connected) return Example.InvalidQueryConnection; - var query = new Query(); try { - var command = new SqlCommand(); - command.Connection = _db; - command.CommandTimeout = Timeout.Query; - command.CommandText = sql; - if (parameters != null) + using (var command = new SqlCommand()) { - foreach (var parameter in parameters) + command.Connection = _db; + command.CommandTimeout = _timeout.Query; + command.CommandText = sql; + if (parameters != null) { - if (parameter != null) command.Parameters.Add(parameter); + foreach (var parameter in parameters) + { + if (parameter != null) command.Parameters.Add(parameter); + } } - } - using (var dataset = new DataSet()) - { - using (var dataadapter = new SqlDataAdapter(sql, _db)) + using (var ds = new DataSet()) { - dataadapter.Fill(dataset, tablename); - query.Table = dataset.Tables[tablename]; + using (var da = new SqlDataAdapter(sql, _db)) + { + const string name = "resule"; + da.Fill(ds, name); + var table = ds.Tables[name]; + return new Query(table, true); + } } } - command.Dispose(); - query.Success = true; } catch (Exception exception) { - LogError("Query", exception, sql); - query.Success = false; - query.Exception = exception; + Logger.Error(nameof(SqlClient), "Query", exception, sql); + return new Query(exception); } - return query; } /// 执行。 @@ -210,119 +259,42 @@ namespace Apewer.Source var connected = Connect(); if (!connected) return Example.InvalidExecuteConnection; - var transaction = _db.BeginTransaction(); - var execute = new Execute(); + var inTransaction = _transaction != null; + if (!inTransaction) Begin(); try { - var command = new SqlCommand(); - command.Connection = _db; - command.Transaction = transaction; - command.CommandTimeout = Timeout.Execute; - command.CommandText = sql; - if (parameters != null) + using (var command = new SqlCommand()) { - foreach (var parameter in parameters) + command.Connection = _db; + command.Transaction = (SqlTransaction)_transaction; + command.CommandTimeout = _timeout.Execute; + command.CommandText = sql; + if (parameters != null) { - if (parameter != null) command.Parameters.Add(parameter); + 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); } - execute.Rows += command.ExecuteNonQuery(); - transaction.Commit(); - command.Dispose(); - execute.Success = true; } catch (Exception exception) { - try { transaction.Rollback(); } catch { } - LogError("Execute", exception, sql); - execute.Success = false; - execute.Exception = exception; + Logger.Error(nameof(SqlClient), "Execute", exception, sql); + if (!inTransaction) Rollback(); + return new Execute(exception); } - try { transaction.Dispose(); } catch { } - return execute; - } - - #endregion - - #region 属性。 - - /// 获取当前的 SqlConnection 对象。 - public SqlConnection Connection - { - get { return _db; } - } - - /// 获取或设置连接字符串。 - public string ConnectionString - { - get { return _connectionstring; } - set { _connectionstring = value ?? ""; _address = ""; _store = ""; _user = ""; _pass = ""; } - } - - /// 获取或设置数据库服务器的地址。 - public string Address - { - get { return _address; } - set { _address = value ?? ""; UpdateConnectString(); } - } - - /// 获取或设置数据库名称。 - public string Store - { - get { return _store; } - set { _store = value ?? ""; UpdateConnectString(); } - } - - /// 获取或设置用于连接数据库服务器的用户名,为空则使用 Windows 用户登录。 - public string User - { - get { return _user; } - set { _user = value ?? ""; UpdateConnectString(); } - } - - /// 获取或设置用于连接数据库服务器的密码。 - public string Pass - { - get { return _pass; } - set { _pass = value ?? ""; UpdateConnectString(); } - } - - /// 获取或设置超时。 - public Timeout Timeout - { - get { return _timeout; } - set { _timeout = value; } } #endregion - #region 方法。 - - /// 指定连接凭据后,是否符合连接要求。 - public bool Proven() - { - return Proven(_address, _store, _user, _pass); - } - - private void UpdateConnectString() - { - _connectionstring = ""; - _connectionstring += "data source = " + _address + "; "; - _connectionstring += "initial catalog = " + _store + "; "; - if (string.IsNullOrEmpty(User)) - { - _connectionstring += "integrated security = sspi; "; - } - else - { - _connectionstring += "user id = " + _user + "; "; - if (!string.IsNullOrEmpty(_pass)) _connectionstring += "password = " + _pass + "; "; - } - _connectionstring += "connection timeout = " + Timeout.Connect.ToString() + ";"; - } + #region ORM /// 查询数据库中的所有表名。 - public List TableNames() + public string[] TableNames() { var list = new List(); if (Connect()) @@ -337,11 +309,11 @@ namespace Apewer.Source } query.Dispose(); } - return list; + return list.ToArray(); } /// 查询数据库实例中的所有数据库名。 - public List StoreNames() + public string[] StoreNames() { var list = new List(); if (Connect()) @@ -360,11 +332,11 @@ namespace Apewer.Source } query.Dispose(); } - return list; + return list.ToArray(); } /// 查询表中的所有列名。 - public List ColumnNames(string tableName) + public string[] ColumnNames(string tableName) { var list = new List(); if (Connect()) @@ -380,21 +352,17 @@ namespace Apewer.Source } query.Dispose(); } - return list; + return list.ToArray(); } /// 创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。 public string Initialize() where T : class, IRecord, new() => Initialize(typeof(T)); - /// 创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。 - public string Initialize(Record model) => model == null ? "参数无效。" : Initialize(model); - /// 创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。 public string Initialize(Type model) { - var structure = null as TableStructure; - try { structure = TableStructure.ParseModel(model); } - catch (Exception exception) { return exception.Message; } + var structure = TableStructure.Parse(model); + if (structure == null) return "无法解析记录模型。"; // 连接数据库。 if (!Connect()) return "连接数据库失败。"; @@ -402,9 +370,9 @@ namespace Apewer.Source // 检查现存表。 var exists = false; var tables = TableNames(); - if (tables.Count > 0) + if (tables.Length > 0) { - var lower = structure.Table.ToLower(); + var lower = structure.Name.ToLower(); foreach (var table in tables) { if (TextUtility.IsBlank(table)) continue; @@ -419,8 +387,8 @@ namespace Apewer.Source if (exists) { // 获取已存在的列名。 - var columns = ColumnNames(structure.Table); - if (columns.Count > 0) + var columns = ColumnNames(structure.Name); + if (columns.Length > 0) { var lower = new List(); foreach (var column in columns) @@ -428,11 +396,11 @@ namespace Apewer.Source if (TextUtility.IsBlank(column)) continue; lower.Add(column.ToLower()); } - columns = lower; + columns = lower.ToArray(); } // 增加列。 - foreach (var column in structure.Columns.Values) + foreach (var column in structure.Columns) { // 检查 Independent 特性。 if (structure.Independent && column.Independent) continue; @@ -444,100 +412,83 @@ namespace Apewer.Source var type = GetColumnDeclaration(column); if (type == TextUtility.Empty) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); - var sql = TextUtility.Merge("alter table [", structure.Table, "] add ", type, "; "); + var sql = TextUtility.Merge("alter table [", structure.Name, "] add ", type, "; "); var execute = Execute(sql); - if (execute.Success == false) return execute.Error; + if (execute.Success == false) return execute.Message; } return TextUtility.Empty; } else { var sqlcolumns = new List(); - foreach (var kvp in structure.Columns) + foreach (var column in structure.Columns) { - var property = kvp.Key; - var column = kvp.Value; - // 检查 Independent 特性。 if (structure.Independent && column.Independent) continue; var type = GetColumnDeclaration(column); - if (!column.Independent && property == "Key") type = type + " primary key"; + if (!column.Independent && column.Property.Name == "Key") type = type + " primary key"; if (type == TextUtility.Empty) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); sqlcolumns.Add(type); } - var sql = TextUtility.Merge("create table [", structure.Table, "](", string.Join(", ", sqlcolumns.ToArray()), "); "); + var sql = TextUtility.Merge("create table [", structure.Name, "](", string.Join(", ", sqlcolumns.ToArray()), "); "); var execute = Execute(sql); if (execute.Success) return TextUtility.Empty; - return execute.Error; + return execute.Message; } } - /// 插入记录。成功时候返回空字符串,发生异常时返回异常信息。 + /// 插入记录。返回错误信息。 public string Insert(IRecord record) { if (record == null) return "参数无效。"; - var type = record.GetType(); - record.FixProperties(); - var structure = null as TableStructure; - try { structure = TableStructure.ParseModel(record); } - catch (Exception exception) { return exception.Message; } - - var parameters = structure.CreateDataParameters(record, CreateDataParameter); - - var sql = GenerateInsertStatement(structure.Table, parameters); + var structure = TableStructure.Parse(record.GetType()); + if (structure == null) return "无法解析记录模型。"; + var parameters = structure.CreateParameters(record, CreateDataParameter); + var sql = GenerateInsertStatement(structure.Name, parameters); var execute = Execute(sql, parameters); if (execute.Success) return TextUtility.Empty; - return execute.Error; + return execute.Message; } - /// - /// 更新记录,实体中的 Created 和 Key 属性不被更新。成功时返回空字符串,发生异常时返回异常信息。 - /// 无法更新拥有 Independent 特性的模型。 - /// + /// 更新记录,实体中的 Key 属性不被更新。返回错误信息。 + /// 无法更新带有 Independent 特性的模型(缺少 Key 属性)。 public string Update(IRecord record) { if (record == null) return "参数无效。"; - var type = record.GetType(); - record.FixProperties(); record.SetUpdated(); - var structure = null as TableStructure; - try { structure = TableStructure.ParseModel(record); } - catch (Exception exception) { return exception.Message; } - - // 检查 Independent 特性。 - if (structure.Independent) return "无法更新拥有 Independent 特性的模型。"; - - var parameters = structure.CreateDataParameters(record, CreateDataParameter, "_created", "_key"); - - var sql = GenerateUpdateStatement(structure.Table, record.Key, parameters); + var structure = TableStructure.Parse(record.GetType()); + if (structure == null) return "无法解析记录模型。"; + if (structure.Independent) return "无法更新带有 Independent 特性的模型。"; + var parameters = structure.CreateParameters(record, CreateDataParameter, "_key"); + var sql = GenerateUpdateStatement(structure.Name, record.Key, parameters); var execute = Execute(sql, parameters); if (execute.Success) return TextUtility.Empty; - return execute.Error; + return execute.Message; } /// 获取按指定语句查询到的所有记录。 - public Result> Query(Type model, string sql) => OrmHelper.Query(this, model, sql); + public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); /// 获取按指定语句查询到的所有记录。 - public Result> Query(string sql) where T : class, IRecord, new() => OrmHelper.Query(this, sql); + public Result Query(string sql) where T : class, IRecord, new() => OrmHelper.Query(this, sql); /// 获取记录。 - public Result> Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => + public 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 Result> Query(long flag = 0) where T : class, IRecord, new() => OrmHelper.Query(this, (tn) => + public Result Query(long flag = 0) where T : class, IRecord, new() => OrmHelper.Query(this, (tn) => { if (flag == 0) return $"select * from [{tn}]; "; return $"select * from [{tn}] where _flag={flag}; "; @@ -558,66 +509,55 @@ namespace Apewer.Source }); /// 查询有效的 Key 值。 - public Result> Keys(Type model, long flag = 0) => OrmHelper.Keys(this, model, (tn) => + public 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 Result> Keys(long flag = 0) where T : class, IRecord, new() => Keys(typeof(T), flag); + public Result Keys(long flag = 0) where T : class, IRecord, new() => Keys(typeof(T), flag); #endregion - #region 静态方法。 + #region public static + +#if NETFRAMEWORK - private static string GetColumnDeclaration(ColumnAttribute column) + /// 枚举本地网络中服务器的名称。 + public static SqlServerSource[] EnumerateServer() { - var type = TextUtility.Empty; - var vcolumn = column; - var length = Math.Max(0, vcolumn.Length); - switch (vcolumn.Type) + var list = new List(); + + // 表中列名:ServerName、InstanceName、IsClustered、Version。 + using (var query = new Query(SqlDataSourceEnumerator.Instance.GetDataSources())) { - 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.VarChar255: - type = TextUtility.Merge("varchar(255)"); - 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.NVarChar255: - type = TextUtility.Merge("nvarchar(255)"); - break; - case ColumnType.NVarCharMax: - type = TextUtility.Merge("nvarchar(max)"); - break; - case ColumnType.NText: - type = TextUtility.Merge("ntext"); - break; - default: - return TextUtility.Empty; + for (int i = 0; i < query.Rows; i++) + { + var item = new SqlServerSource(); + item.ServerName = query.Text(i, "ServerName"); + list.Add(item); + } } - return TextUtility.Merge("[", vcolumn.Field, "] ", type); + 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; } /// 创建参数。 @@ -651,12 +591,12 @@ namespace Apewer.Source vtype = SqlDbType.DateTime; break; case ColumnType.VarChar: - case ColumnType.VarChar255: + case ColumnType.VarChar191: case ColumnType.VarCharMax: vtype = SqlDbType.VarChar; break; case ColumnType.NVarChar: - case ColumnType.NVarChar255: + case ColumnType.NVarChar191: case ColumnType.NVarCharMax: vtype = SqlDbType.VarChar; break; @@ -679,9 +619,9 @@ namespace Apewer.Source case ColumnType.NVarChar: vsize = NumberUtility.Restrict(vsize, 0, 4000); break; - case ColumnType.VarChar255: - case ColumnType.NVarChar255: - vsize = NumberUtility.Restrict(vsize, 0, 255); + case ColumnType.VarChar191: + case ColumnType.NVarChar191: + vsize = NumberUtility.Restrict(vsize, 0, 191); break; default: vsize = 0; @@ -728,51 +668,60 @@ namespace Apewer.Source return p; } - ///// 枚举本地网络中服务器的名称。 - //public static List EnumerateServer() - //{ - // // 表中列名:ServerName、InstanceName、IsClustered、Version。 - // var table = SqlDataSourceEnumerator.Instance.GetDataSources(); - // var query = new Query(); - // query.Success = table != null; - // query.Table = table; - // var list = new List(); - // for (int i = 0; i < query.Rows; i++) - // { - // var sn = query.Text(i, "ServerName"); - // if (!string.IsNullOrEmpty(sn)) list.Add(sn); - // } - // query.Dispose(); - // return list; - //} - - /// 指定的连接凭据是否符合连接要求。 - public static bool Proven(SqlClinet sqlserver) - { - return Proven(sqlserver._address, sqlserver._store, sqlserver._user, sqlserver._pass); - } + #endregion - /// 指定的连接凭据是否符合连接要求,默认指定 master 数据库。 - public static bool Proven(string address, string user, string pass) => Proven(address, "master", user, pass); + #region private - /// 指定的连接凭据是否符合连接要求。 - public static bool Proven(string address, string store, string user, string pass) + static string GetColumnDeclaration(ColumnAttribute column) { - 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; + 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(255)"); + 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); } - #endregion - - #region Linq Utility - - private static string GetParameterName(string parameter) + static string GetParameterName(string parameter) { var name = TextUtility.AntiInject(parameter, 255); if (name.StartsWith("@") && name.Length > 1) @@ -782,7 +731,7 @@ namespace Apewer.Source return name; } - private static string GetParameterName(IDataParameter parameter) + static string GetParameterName(IDataParameter parameter) { var name = TextUtility.Empty; if (parameter != null) @@ -792,7 +741,7 @@ namespace Apewer.Source return name; } - private static List GetParametersNames(IEnumerable parameters) + static string[] GetParametersNames(IEnumerable parameters) { var columns = new List(); if (parameters != null) @@ -805,10 +754,10 @@ namespace Apewer.Source columns.Add(name); } } - return columns; + return columns.ToArray(); } - private static string GenerateInsertStatement(string table, List columns) + static string GenerateInsertStatement(string table, string[] columns) { var result = TextUtility.Empty; var vtable = TextUtility.AntiInject(table, 255); @@ -835,7 +784,7 @@ namespace Apewer.Source return result; } - private static string GenerateUpdateStatement(string table, string key, List columns) + static string GenerateUpdateStatement(string table, string key, string[] columns) { var result = TextUtility.Empty; var vtable = TextUtility.AntiInject(table, 255); @@ -858,14 +807,14 @@ namespace Apewer.Source /// 生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。 /// /// - private static string GenerateInsertStatement(string table, IEnumerable parameters) + static string GenerateInsertStatement(string table, IEnumerable parameters) { if (table == null) throw new ArgumentNullException(nameof(table)); var tableName = TextUtility.AntiInject(table, 255); if (TextUtility.IsBlank(tableName)) throw new ArgumentException("表名无效。", nameof(table)); var vcolumns = GetParametersNames(parameters); - if (vcolumns.Count < 1) return TextUtility.Empty; + if (vcolumns.Length < 1) return TextUtility.Empty; return GenerateInsertStatement(tableName, vcolumns); } @@ -873,7 +822,7 @@ namespace Apewer.Source /// 生成 UPDATE 语句,键字段名为“_key”。表名必须有效,键值必须有效,无有效参数时将获取空结果。 /// /// - private static string GenerateUpdateStatement(string table, string key, IEnumerable parameters) + static string GenerateUpdateStatement(string table, string key, IEnumerable parameters) { if (table == null) throw new ArgumentNullException(nameof(table)); var t = TextUtility.AntiInject(table, 255); @@ -884,7 +833,7 @@ namespace Apewer.Source if (TextUtility.IsBlank(k)) throw new ArgumentException("键值无效。", nameof(table)); var columes = GetParametersNames(parameters); - if (columes.Count < 1) return TextUtility.Empty; + if (columes.Length < 1) return TextUtility.Empty; return GenerateUpdateStatement(t, k, columes); } @@ -894,5 +843,3 @@ namespace Apewer.Source } } - -#endif diff --git a/Apewer.Source/Source/SqlServerSouce.cs b/Apewer.Source/Source/SqlServerSouce.cs new file mode 100644 index 0000000..561caa8 --- /dev/null +++ b/Apewer.Source/Source/SqlServerSouce.cs @@ -0,0 +1,26 @@ +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 fc60778..bb0b09d 100644 --- a/Apewer.Source/Source/Sqlite.cs +++ b/Apewer.Source/Source/Sqlite.cs @@ -1,8 +1,9 @@ -/* 2021.09.23 */ +/* 2021.10.14 */ using System; using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.Data.SQLite; using System.Text; //using Mono.Data.Sqlite; @@ -11,73 +12,50 @@ namespace Apewer.Source { /// 用于快速连接 SQLite 数据库的辅助。 - public class Sqlite : IDatabase + public class Sqlite : IDbClient { - #region 变量定义。 + #region 基础 - private SQLiteConnection _db = null; - private object _locker = new object(); - - private Timeout _timeout = new Timeout(); + private Timeout _timeout = null; private string _connstring = ""; private string _path = ""; private string _pass = ""; - private byte[] _passdata = BytesUtility.Empty; - - #endregion - - #region this - - private void VarInit(string path, Timeout timeout, string pass, byte[] passData) - { - _path = path ?? ""; - _passdata = (passData == null) ? BytesUtility.Empty : passData; - _pass = pass ?? ""; - _timeout = timeout; - } - - /// 连接内存。 - public Sqlite() => VarInit(Memory, new Timeout(), null, null); - /// 连接指定文件。 - public Sqlite(string path) => VarInit(path, new Timeout(), null, null); - - /// 连接指定文件。 - private Sqlite(string path, byte[] passData) => VarInit(path, new Timeout(), null, passData); - - /// 连接指定文件。 - public Sqlite(string path, string pass) => VarInit(path, new Timeout(), pass, null); - - /// 连接指定文件。 - public Sqlite(string path, Timeout timeout) => VarInit(path, timeout, null, null); - - #endregion - - #region 日志。 + private object _locker = new object(); /// 获取或设置日志记录。 public Logger Logger { get; set; } - private void LogError(string action, Exception ex, string addtion) - { - var logger = Logger; - if (logger != null) logger.Error(this, "SQLite", action, ex.GetType().FullName, ex.Message, addtion); - } + /// 超时设定。 + public Timeout Timeout { get => _timeout; } - private void LogError(string action, string message) + /// 创建连接实例。 + /// 注意:
- 构造函数不会创建不存在的文件;
- 参数 path 为文件路径,指定为空时将使用 :memory: 作为路径连接内存。
+ public Sqlite(string path = null, string pass = null, Timeout timeout = null) { - var logger = Logger; - if (logger != null) logger.Error(this, "SQLite", action, message); + _timeout = timeout ?? Timeout.Default; + _path = path.IsEmpty() ? Memory : path; + _pass = pass; + if (pass.IsEmpty()) _connstring = $"data source='{_path}'; password={_pass}; version=3; "; + else _connstring = $"data source='{_path}'; password={_pass}; version=3; "; } #endregion - #region 实现接口。 + #region 连接 - /// 数据库是否已经连接。 + private SQLiteConnection _db = null; + + /// 数据库已经连接。 public bool Online { get => _db != null && _db.State == ConnectionState.Open; } + /// 连接字符串。 + public string ConnectionString { get => _connstring; } + + /// 获取当前的 SQLiteConnection 对象。 + public IDbConnection Connection { get => _db; } + /// 连接数据库,若未连接则尝试连接。 /// 是否已连接。 public bool Connect() @@ -86,10 +64,6 @@ namespace Apewer.Source { _db = new SQLiteConnection(); _db.ConnectionString = ConnectionString; - //if (string.IsNullOrEmpty(_connstring) && string.IsNullOrEmpty(_pass) && (_passdata.Length > 0)) - //{ - // _db.SetPassword(_pass); - //} } else { @@ -106,7 +80,7 @@ namespace Apewer.Source } catch (Exception ex) { - LogError("Connection", ex, _db.ConnectionString); + Logger.Error(nameof(Sqlite), "Connection", ex, _db.ConnectionString); Close(); return false; } @@ -117,6 +91,11 @@ namespace Apewer.Source { if (_db != null) { + if (_transaction != null) + { + if (_autocommit) Commit(); + else Rollback(); + } lock (_locker) { _db.Dispose(); @@ -128,6 +107,78 @@ namespace Apewer.Source /// 关闭连接,释放对象所占用的系统资源,并清除连接信息。 public void Dispose() { Close(); } + #endregion + + #region Transaction + + private IDbTransaction _transaction = null; + private bool _autocommit = false; + + /// 启动事务。 + public string Begin(bool commit = true) => Begin(commit, null); + + /// 启动事务。 + public string Begin(bool commit, Class isolation) + { + if (!Connect()) return "未连接。"; + if (_transaction != null) return "存在已启动的事务,无法再次启动。"; + try + { + _transaction = isolation ? _db.BeginTransaction(isolation.Value) : _db.BeginTransaction(); + _autocommit = commit; + return null; + } + catch (Exception ex) + { + Logger.Error(nameof(Sqlite), "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(nameof(Sqlite), "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(nameof(Sqlite), "Rollback", ex.Message()); + return ex.Message(); + } + } + + #endregion + + #region SQL + /// 查询。 public IQuery Query(string sql) => Query(sql, null); @@ -136,43 +187,41 @@ namespace Apewer.Source { if (string.IsNullOrEmpty(sql)) return Example.InvalidQueryStatement; - const string table = "result"; - var connected = Connect(); if (!connected) return Example.InvalidQueryConnection; var query = new Query(); try { - var command = new SQLiteCommand(); - command.Connection = _db; - command.CommandTimeout = Timeout.Query; - command.CommandText = sql; - if (parameters != null) + using (var command = new SQLiteCommand()) { - foreach (var p in parameters) + command.Connection = _db; + command.CommandTimeout = _timeout.Query; + command.CommandText = sql; + if (parameters != null) { - if (p != null) command.Parameters.Add(p); + foreach (var p in parameters) + { + if (p != null) command.Parameters.Add(p); + } } - } - using (var dataset = new DataSet()) - { - using (var da = new SQLiteDataAdapter(sql, _db)) + using (var dataset = new DataSet()) { - da.Fill(dataset, table); - query.Table = dataset.Tables[table]; + using (var da = new SQLiteDataAdapter(sql, _db)) + { + const string name = "result"; + da.Fill(dataset, name); + var table = dataset.Tables[name]; + return new Query(table); + } } } - command.Dispose(); - query.Success = true; } catch (Exception ex) { - LogError("Query", ex, sql); - query.Success = false; - query.Exception = ex; + Logger.Error(nameof(Sqlite), "Query", ex, sql); + return new Query(ex); } - return query; } /// 执行单条 Transact-SQL 语句。 @@ -188,36 +237,34 @@ namespace Apewer.Source lock (_locker) { - var transaction = _db.BeginTransaction(); - var execute = new Execute(); + var inTransaction = _transaction != null; + if (!inTransaction) Begin(); try { - var command = new SQLiteCommand(); - command.Connection = _db; - command.Transaction = transaction; - command.CommandTimeout = Timeout.Execute; - command.CommandText = sql; - if (parameters != null) + using (var command = new SQLiteCommand()) { - foreach (var p in parameters) + command.Connection = _db; + command.Transaction = (SQLiteTransaction)_transaction; + command.CommandTimeout = _timeout.Execute; + command.CommandText = sql; + if (parameters != null) { - if (p != null) command.Parameters.Add(p); + foreach (var p in parameters) + { + if (p != null) command.Parameters.Add(p); + } } + var rows = command.ExecuteNonQuery(); + if (!inTransaction) Commit(); // todo 此处应该检查事务提交产生的错误。 + return new Execute(true, rows); } - execute.Rows += command.ExecuteNonQuery(); - transaction.Commit(); - command.Dispose(); - execute.Success = true; } catch (Exception ex) { - try { transaction.Rollback(); } catch { } - LogError("Execute", ex, sql); - execute.Success = false; - execute.Exception = ex; + Logger.Error(nameof(Sqlite), "Execute", ex, sql); + if (!inTransaction) Rollback(); + return new Execute(ex); } - try { transaction.Dispose(); } catch { } - return execute; } } @@ -225,72 +272,12 @@ namespace Apewer.Source #region 属性。 - /// 获取当前的 SQLiteConnection 对象。 - public IDbConnection Connection { get => _db; } - - /// 获取或设置超时。 - public Timeout Timeout { get => _timeout; set => _timeout = value; } - - /// 获取或设置连接字符串,连接字符串非空时将忽略 Path 属性。数据库在线时无法设置。 - public string ConnectionString - { - get - { - if (string.IsNullOrEmpty(_connstring)) - { - var temp = new StringBuilder(); - temp.Append("data source='", _path, "'; version=3; "); - if (!string.IsNullOrEmpty(_pass)) temp.Append("password=", _pass, "; "); - return temp.ToString(); - } - else return _connstring; - } - set - { - if (Online) return; - _connstring = string.IsNullOrEmpty(value) ? "" : value; - } - } - - /// 获取或设置数据库路径(文件或内存)。数据库在线时无法设置。 - public string Path - { - get { return _path; } - set - { - if (Online) return; - _path = string.IsNullOrEmpty(value) ? "" : value; - } - } - - /// 获取或设置数据库密码。数据库在线时无法设置。 - public string Password - { - get { return _pass; } - set - { - if (Online) return; - _pass = string.IsNullOrEmpty(value) ? "" : value; - } - } - - /// 获取或设置数据库密码。数据库在线时无法设置。 - private byte[] PasswordData - { - get { return _passdata; } - set - { - if (Online) return; - _passdata = (value == null) ? BytesUtility.Empty : value; - } - } - /// 保存当前数据库到文件,若文件已存在则将重写文件。 public bool Save(string path, string pass = null) { if (!StorageUtility.CreateFile(path, 0, true)) { - LogError("Save", TextUtility.Merge("创建文件 ", path, " 失败。")); + Logger.Error(nameof(Sqlite), "Save", TextUtility.Merge("创建文件 ", path, " 失败。")); return false; } @@ -311,10 +298,10 @@ namespace Apewer.Source #endregion - #region ORM。 + #region ORM /// 查询数据库中的所有表名。 - public List TableNames() + public string[] TableNames() { var list = new List(); if (Connect()) @@ -329,11 +316,11 @@ namespace Apewer.Source } query.Dispose(); } - return list; + return list.ToArray(); } /// 查询数据库中的所有视图名。 - public List ViewNames() + public string[] ViewNames() { var list = new List(); if (Connect()) @@ -348,11 +335,11 @@ namespace Apewer.Source } query.Dispose(); } - return list; + return list.ToArray(); } /// 查询表中的所有列名。 - public List ColumnNames(string table) + public string[] ColumnNames(string table) { var list = new List(); if (Connect()) @@ -369,7 +356,7 @@ namespace Apewer.Source } } } - return list; + return list.ToArray(); } /// 创建表,不修改已存在表。成功时返回空字符串,发生异常时返回异常信息。 @@ -381,9 +368,8 @@ namespace Apewer.Source /// 创建表,不修改已存在表。当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。 public string Initialize(Type model) { - var structure = null as TableStructure; - try { structure = TableStructure.ParseModel(model); } - catch (Exception exception) { return exception.Message; } + var structure = TableStructure.Parse(model); + if (structure == null) return "无法解析记录模型。"; // 连接数据库。 if (!Connect()) return "连接数据库失败。"; @@ -391,9 +377,9 @@ namespace Apewer.Source // 检查现存表。 var exists = false; var tables = TableNames(); - if (tables.Count > 0) + if (tables.Length > 0) { - var lower = structure.Table.ToLower(); + var lower = structure.Name.ToLower(); foreach (var table in tables) { if (TextUtility.IsBlank(table)) continue; @@ -412,73 +398,69 @@ namespace Apewer.Source else { var sqlcolumns = new List(); - foreach (var column in structure.Columns.Values) + foreach (var column in structure.Columns) { var type = GetColumnDeclaration(column); if (type == TextUtility.Empty) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); sqlcolumns.Add(type); } - var sql = TextUtility.Merge("create table [", structure.Table, "](", TextUtility.Join(", ", sqlcolumns), "); "); + var sql = TextUtility.Merge("create table [", structure.Name, "](", TextUtility.Join(", ", sqlcolumns), "); "); var execute = Execute(sql); if (execute.Success) return TextUtility.Empty; - return execute.Error; + return execute.Message; } } - /// 插入记录。成功时候返回空字符串,发生异常时返回异常信息。 + /// 插入记录。返回错误信息。 public string Insert(IRecord record) { if (record == null) return "参数无效。"; record.FixProperties(); - var structure = null as TableStructure; - try { structure = TableStructure.ParseModel(record); } - catch (Exception exception) { return exception.Message; } - - var parameters = structure.CreateDataParameters(record, CreateDataParameter); - - var sql = GenerateInsertStatement(structure.Table, (IEnumerable)parameters); + var structure = TableStructure.Parse(record.GetType()); + if (structure == null) return "无法解析记录模型。"; + var parameters = structure.CreateParameters(record, CreateDataParameter); + var sql = GenerateInsertStatement(structure.Name, (IEnumerable)parameters); var execute = Execute(sql, parameters); if (execute.Success && execute.Rows > 0) return TextUtility.Empty; - return execute.Error; + return execute.Message; } - /// 更新记录,实体中的 Created 和 Key 属性不被更新。成功时返回空字符串,发生异常时返回异常信息。 + /// 更新记录,实体中的 Key 属性不被更新。返回错误信息。 + /// 无法更新带有 Independent 特性的模型(缺少 Key 属性)。 public string Update(IRecord record) { if (record == null) return "参数无效。"; record.FixProperties(); record.SetUpdated(); - var structure = null as TableStructure; - try { structure = TableStructure.ParseModel(record); } - catch (Exception exception) { return exception.Message; } - - var parameters = structure.CreateDataParameters(record, CreateDataParameter, "_created", "_key"); - - var sql = GenerateUpdateStatement(structure.Table, record.Key, parameters); + var structure = TableStructure.Parse(record.GetType()); + if (structure == null) return "无法解析记录模型。"; + if (structure.Independent) return "无法更新带有 Independent 特性的模型。"; + var parameters = structure.CreateParameters(record, CreateDataParameter, "_key"); + var sql = GenerateUpdateStatement(structure.Name, record.Key, parameters); var execute = Execute(sql, parameters); if (execute.Success && execute.Rows > 0) return TextUtility.Empty; - return execute.Error; + return execute.Message; } /// 获取按指定语句查询到的所有记录。 - public Result> Query(Type model, string sql) => OrmHelper.Query(this, model, sql); + public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); /// 获取按指定语句查询到的所有记录。 - public Result> Query(string sql) where T : class, IRecord, new() => OrmHelper.Query(this, sql); + public Result Query(string sql) where T : class, IRecord, new() => OrmHelper.Query(this, sql); /// 查询多条记录。 - public Result> Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => + public 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 Result> Query(long flag = 0) where T : class, IRecord, new() => OrmHelper.Query(this, (tn) => + public Result Query(long flag = 0) where T : class, IRecord, new() => OrmHelper.Query(this, (tn) => { if (flag == 0) return $"select * from [{tn}]; "; return $"select * from [{tn}] where _flag={flag}; "; @@ -499,14 +481,14 @@ namespace Apewer.Source }); /// 获取指定类型的主键,按 Flag 属性筛选。 - public Result> Keys(Type model, long flag = 0) => OrmHelper.Keys(this, model, (tn) => + public Result Keys(Type model, long flag = 0) => OrmHelper.Keys(this, model, (tn) => { if (flag == 0) return $"select _key from [{tn}] where _flag={flag}; "; return $"select _key from [{tn}]; "; }); /// >获取指定类型的主键,按 Flag 属性筛选。 - public Result> Keys(long flag = 0) where T : class, IRecord, new() => Keys(typeof(T), flag); + public Result Keys(long flag = 0) where T : class, IRecord, new() => Keys(typeof(T), flag); #endregion @@ -553,13 +535,13 @@ namespace Apewer.Source case ColumnType.Float: return "float"; case ColumnType.VarChar: - case ColumnType.VarChar255: + case ColumnType.VarChar191: case ColumnType.VarCharMax: return "varchar"; case ColumnType.Text: return "text"; case ColumnType.NVarChar: - case ColumnType.NVarChar255: + case ColumnType.NVarChar191: case ColumnType.NVarCharMax: return "nvarchar"; case ColumnType.NText: @@ -585,9 +567,11 @@ namespace Apewer.Source type = "real"; break; case ColumnType.VarChar: - case ColumnType.VarChar255: type = TextUtility.Merge("varchar(", length, ")"); break; + case ColumnType.VarChar191: + type = TextUtility.Merge("varchar(191)"); + break; case ColumnType.VarCharMax: type = TextUtility.Merge("varchar(255)"); break; @@ -595,9 +579,11 @@ namespace Apewer.Source type = TextUtility.Merge("text"); break; case ColumnType.NVarChar: - case ColumnType.NVarChar255: type = TextUtility.Merge("nvarchar(", length, ")"); break; + case ColumnType.NVarChar191: + type = TextUtility.Merge("nvarchar(191)"); + break; case ColumnType.NVarCharMax: type = TextUtility.Merge("nvarchar(255)"); break; @@ -636,9 +622,11 @@ namespace Apewer.Source case ColumnType.NVarChar: s = NumberUtility.Restrict(s, 0, 4000); break; - case ColumnType.VarChar255: + case ColumnType.VarChar191: + case ColumnType.NVarChar191: + s = NumberUtility.Restrict(s, 0, 191); + break; case ColumnType.VarCharMax: - case ColumnType.NVarChar255: case ColumnType.NVarCharMax: s = NumberUtility.Restrict(s, 0, 255); break; @@ -742,7 +730,22 @@ namespace Apewer.Source #endregion - #region ORM + #region 生成 SQL 语句 + + /// 生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。 + /// + /// + public static string GenerateInsertStatement(string table, IEnumerable parameters) + { + if (table == null) throw new ArgumentNullException(nameof(table)); + var t = TextUtility.AntiInject(table, 255); + if (TextUtility.IsBlank(t)) throw new ArgumentException("表名无效。", nameof(table)); + + var cs = GetParametersNames(parameters); + if (cs.Count < 1) return TextUtility.Empty; + + return GenerateInsertStatement(t, cs); + } private static string GetParameterName(string parameter) { @@ -807,21 +810,6 @@ namespace Apewer.Source return r; } - /// 生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。 - /// - /// - public static string GenerateInsertStatement(string table, IEnumerable parameters) - { - if (table == null) throw new ArgumentNullException(nameof(table)); - var t = TextUtility.AntiInject(table, 255); - if (TextUtility.IsBlank(t)) throw new ArgumentException("表名无效。", nameof(table)); - - var cs = GetParametersNames(parameters); - if (cs.Count < 1) return TextUtility.Empty; - - return GenerateInsertStatement(t, cs); - } - private static string GenerateUpdateStatement(string table, string key, List columns) { var result = TextUtility.Empty; diff --git a/Apewer.Web/Internals/ApiHelper.cs b/Apewer.Web/Internals/ApiHelper.cs index 0df0df9..23e4d8b 100644 --- a/Apewer.Web/Internals/ApiHelper.cs +++ b/Apewer.Web/Internals/ApiHelper.cs @@ -301,7 +301,7 @@ namespace Apewer.Internals #region Response - static StringPairs MergeHeaders(ApiOptions options, ApiResponse response) + static StringPairs PrepareHeaders(ApiOptions options, ApiResponse response) { var merged = new StringPairs(); if (options != null) @@ -322,6 +322,12 @@ namespace Apewer.Internals { merged.Add("X-Content-Type-Options", "nosniff"); } + + // 用于客户端,当前页面使用 HTTPS 时,将资源升级为 HTTPS。 + if (options.UpgradeHttps) + { + merged.Add("Content-Security-Policy", "upgrade-insecure-requests"); + } } if (response != null) { @@ -397,7 +403,7 @@ namespace Apewer.Internals var preOutput = provider.PreWrite(); if (!string.IsNullOrEmpty(preOutput)) return; - var headers = MergeHeaders(options, null); + var headers = PrepareHeaders(options, null); foreach (var header in headers) provider.SetHeader(header.Key, header.Value); provider.SetCache(0); @@ -414,7 +420,7 @@ namespace Apewer.Internals if (!string.IsNullOrEmpty(preOutput)) return; // 设置头。 - var headers = MergeHeaders(options, null); + var headers = PrepareHeaders(options, null); foreach (var header in headers) provider.SetHeader(header.Key, header.Value); var model = response.Model; diff --git a/Apewer.Web/Web/ApiEntries.cs b/Apewer.Web/Web/ApiEntries.cs index 6fe980d..4101391 100644 --- a/Apewer.Web/Web/ApiEntries.cs +++ b/Apewer.Web/Web/ApiEntries.cs @@ -72,7 +72,7 @@ namespace Apewer.Web public static ApiEntries From(Assembly assembly) { if (assembly == null) return null; - var types = RuntimeUtility.GetTypes(assembly, true); + var types = RuntimeUtility.GetTypes(assembly, false); var dict = new Dictionary(); foreach (var type in types) { diff --git a/Apewer.Web/Web/ApiProcessor.cs b/Apewer.Web/Web/ApiProcessor.cs index 4477099..f843e38 100644 --- a/Apewer.Web/Web/ApiProcessor.cs +++ b/Apewer.Web/Web/ApiProcessor.cs @@ -261,6 +261,14 @@ namespace Apewer.Web return; } + // 未知类型,尝试 Json 类型。 + var json = result as Json; + if (json != null) + { + response.Data = json; + return; + } + // 未知返回类型,无法明确输出格式,忽略。 } else diff --git a/Apewer.Web/Web/ApiProgram.cs b/Apewer.Web/Web/ApiProgram.cs index e1f9def..0a88a60 100644 --- a/Apewer.Web/Web/ApiProgram.cs +++ b/Apewer.Web/Web/ApiProgram.cs @@ -17,13 +17,13 @@ namespace Apewer.Web private static ApiInvoker _invoker = new ApiInvoker() { Logger = new Logger(), Options = new ApiOptions() }; /// API 选项。 - protected static ApiOptions Options { get => _invoker.Options; } + public static ApiOptions Options { get => _invoker.Options; } /// 日志记录器。 - protected static Logger Logger { get => _invoker.Logger; } + public static Logger Logger { get => _invoker.Logger; } /// 获取或设置 API 入口。 - protected static ApiEntries Entries { get => _invoker.Entries; set => _invoker.Entries = value; } + public static ApiEntries Entries { get => _invoker.Entries; set => _invoker.Entries = value; } private Action _initializer = null; diff --git a/Apewer/ArrayBuilder.cs b/Apewer/ArrayBuilder.cs index 0a490ad..352a56a 100644 --- a/Apewer/ArrayBuilder.cs +++ b/Apewer/ArrayBuilder.cs @@ -6,7 +6,7 @@ namespace Apewer { /// 数组构建器。 - public class ArrayBuilder + public sealed class ArrayBuilder { private T[] _array; @@ -35,6 +35,25 @@ namespace Apewer if (_count > 0) Array.Copy(old._array, _array, _count); } + /// 获取或设置指定位置的元素,索引器范围为 [0, Length)。 + /// + public T this[int index] + { + get + { + if (index < 0 || index >= _count) throw new ArgumentOutOfRangeException("索引超出了当前数组的范围。"); + return _array[index]; + } + set + { + if (index < 0 || index >= _count) throw new ArgumentOutOfRangeException("索引超出了当前数组的范围。"); + _array[index] = value; + } + } + + /// 缓冲区的容量。 + public int Capacity { get => _capacity; } + /// 当前的元素数量。 public int Length { get => _count; } @@ -95,6 +114,13 @@ namespace Apewer _count += length; } + /// 添加多个元素。 + public void Add(IEnumerable items) + { + if (items == null) return; + foreach (var item in items) Add(item); + } + /// 清空所有元素。 public void Clear() { @@ -138,6 +164,9 @@ namespace Apewer /// 克隆当前实例,生成新实例。 public ArrayBuilder Clone() => new ArrayBuilder(this); + /// 使用 Export 方法实现从 ArrayBuilder<T> 到 T[] 的隐式转换。 + public static implicit operator T[](ArrayBuilder instance) => instance == null ? null : instance.Export(); + } } diff --git a/Apewer/ClockUtility.cs b/Apewer/ClockUtility.cs index 90fc928..ce2d3d1 100644 --- a/Apewer/ClockUtility.cs +++ b/Apewer/ClockUtility.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Text; namespace Apewer @@ -104,7 +105,7 @@ namespace Apewer /// 从毫秒时间戳获取 DateTime 对象。发生异常且不允许异常时将返回 1970-01-01 00:00:00.000。 /// - public static DateTime FromStamp(long stamp, bool exceptable = true) + public static DateTime FromStamp(long stamp, bool throwException = true) { try { @@ -113,13 +114,47 @@ namespace Apewer } catch { - if (exceptable) throw new ArgumentOutOfRangeException(); + if (throwException) throw new ArgumentOutOfRangeException(); return Origin; } } #endregion + #region Text + + /// 解析文本,获取 DateTime 对象。 + public static Class FromText(string text) + { + var str = text; + if (string.IsNullOrEmpty(str)) return null; + + var utc = false; + var lower = str.ToLower(); + if (lower.EndsWith(" utc")) + { + utc = true; + str = str.Substring(0, str.Length - 4); + } + + DateTime dt; + if (!DateTime.TryParse(str, out dt)) + { + if (!str.Contains("-") && DateTime.TryParseExact(str, "yyyy-M-d", null, DateTimeStyles.None, out dt)) + { + if (!str.Contains("/") && DateTime.TryParseExact(str, "yyyy/M/d", null, DateTimeStyles.None, out dt)) + { + return null; + } + } + } + + if (utc) dt = new DateTime(dt.Ticks, DateTimeKind.Utc); + return new Class(dt); + } + + #endregion + #region Lucid & Compact /// 表示当前本地时间的文本,显示为易于阅读的格式。 diff --git a/Apewer/Json.cs b/Apewer/Json.cs index 537d69d..96de6a5 100644 --- a/Apewer/Json.cs +++ b/Apewer/Json.cs @@ -276,32 +276,32 @@ namespace Apewer #region Private Get - private List PrivateGetProperties { get { return GetProperties(); } } + private Json[] PrivateGetProperties { get { return GetProperties(); } } - private List PrivateGetValues { get { return GetValues(); } } + private Json[] PrivateGetValues { get { return GetValues(); } } - private List PrivateGetObjects { get { return GetObjects(); } } + private Json[] PrivateGetObjects { get { return GetObjects(); } } - private List PrivateGetItems { get { return GetItems(); } } + private Json[] PrivateGetItems { get { return GetItems(); } } #endregion #region Object : Get/Set /// 获取所有类型为 Property 的子项。 - public List GetProperties() + public Json[] GetProperties() { - var list = new List(); + var ab = new ArrayBuilder(); if (_jobject != null) { var children = _jobject.Children(); foreach (var child in children) { var json = new Json(child); - list.Add(json); + ab.Add(json); } } - return list; + return ab.Export(); } /// 当前实例类型为 Object 时搜索属性,失败时返回 Null。 @@ -690,51 +690,51 @@ namespace Apewer #region Array /// 获取所有类型为 Value 的子项。 - public List GetValues() + public Json[] GetValues() { - var list = new List(); + var ab = new ArrayBuilder(); if (_jarray != null) { var children = _jarray.Children(); foreach (var child in children) { var json = new Json(child); - list.Add(json); + ab.Add(json); } } - return list; + return ab.Export(); } /// 获取所有类型为 Object 的子项。 - public List GetObjects() + public Json[] GetObjects() { - var list = new List(); + var ab = new ArrayBuilder(); if (_jarray != null) { var children = _jarray.Children(); foreach (var child in children) { var json = new Json(child); - list.Add(json); + ab.Add(json); } } - return list; + return ab.Export(); } /// 获取 Array 中的所有元素。 - public List GetItems() + public Json[] GetItems() { - var list = new List(); + var ab = new ArrayBuilder(); if (_jarray != null) { var children = _jarray.Children(); foreach (var child in children) { var json = new Json(child); - list.Add(json); + ab.Add(json); } } - return list; + return ab.Export(); } /// 当前实例类型为 Array 时添加 Null 元素。 @@ -1116,8 +1116,7 @@ namespace Apewer public static Json From(IList entity, bool lower = false, int depth = -1, bool force = false) { if (entity == null) return null; - var recursive = new List(); - recursive.Add(entity); + var recursive = new object[] { entity }; return From(entity, lower, recursive, depth, force); } @@ -1129,8 +1128,7 @@ namespace Apewer public static Json From(IDictionary entity, bool lower = false, int depth = -1, bool force = false) { if (entity == null) return null; - var recursive = new List(); - recursive.Add(entity); + var recursive = new object[] { entity }; return From(entity, lower, recursive, depth, force); } @@ -1146,12 +1144,11 @@ namespace Apewer public static Json From(object entity, bool lower = false, int depth = -1, bool force = false) { if (entity == null) return null; - var recursive = new List(); - recursive.Add(entity); + var recursive = new object[] { entity }; return From(entity, lower, recursive, depth, force); } - private static Json From(IList list, bool lower, List previous, int depth, bool force) + private static Json From(IList list, bool lower, object[] previous, int depth, bool force) { if (list == null) return null; if (list is IToJson) return ((IToJson)list).ToJson(); @@ -1187,13 +1184,13 @@ namespace Apewer if (recursively) continue; // 处理 Type 对象。 - if (value.GetType().Equals(typeof(Type)) && (previous.Count > 2)) + if (value.GetType().Equals(typeof(Type)) && (previous.Length > 2)) { value = ((Type)value).FullName; } // 处理 Assembly 对象。 - if (value.GetType().Equals(typeof(Assembly)) && (previous.Count > 2)) + if (value.GetType().Equals(typeof(Assembly)) && (previous.Length > 2)) { value = ((Assembly)value).FullName; } @@ -1210,10 +1207,10 @@ namespace Apewer else if (value is Json) { json.AddItem(value as Json); } else { - if ((depth < 0) || (0 < depth && previous.Count < depth)) + if ((depth < 0) || (0 < depth && previous.Length < depth)) { - var recursive = new List(); - recursive.AddRange(previous); + var recursive = new ArrayBuilder(); + recursive.Add(previous); recursive.Add(value); if (value is IDictionary) { json.AddItem(From(value as IDictionary, lower, recursive, depth, force)); } @@ -1231,7 +1228,7 @@ namespace Apewer return json; } - private static Json From(IDictionary dictionary, bool lower, List previous, int depth, bool force) + private static Json From(IDictionary dictionary, bool lower, object[] previous, int depth, bool force) { if (dictionary == null) return null; if (dictionary is IToJson) return ((IToJson)dictionary).ToJson(); @@ -1286,13 +1283,13 @@ namespace Apewer if (value != null) { // 处理 Type 对象。 - if (value.GetType().Equals(typeof(Type)) && (previous.Count > 2)) + if (value.GetType().Equals(typeof(Type)) && (previous.Length > 2)) { value = ((Type)value).FullName; } // 处理 Assembly 对象。 - if (value.GetType().Equals(typeof(Assembly)) && (previous.Count > 2)) + if (value.GetType().Equals(typeof(Assembly)) && (previous.Length > 2)) { value = ((Assembly)value).FullName; } @@ -1310,10 +1307,10 @@ namespace Apewer else if (value is Json) { json.SetProperty(field, value as Json); } else { - if ((depth < 0) || (0 < depth && previous.Count < depth)) + if ((depth < 0) || (0 < depth && previous.Length < depth)) { - var recursive = new List(); - recursive.AddRange(previous); + var recursive = new ArrayBuilder(); + recursive.Add(previous); recursive.Add(value); if (value is IDictionary) { json.SetProperty(field, From(value as IDictionary, lower, recursive, depth, force)); } @@ -1333,7 +1330,7 @@ namespace Apewer return json; } - private static Json From(object entity, bool lower, List previous, int depth, bool force) + private static Json From(object entity, bool lower, object[] previous, int depth, bool force) { if (entity == null) return null; if (entity is IToJson) return ((IToJson)entity).ToJson(); @@ -1348,7 +1345,7 @@ namespace Apewer } if (entity is Json) { if (lower) Lower(entity as Json); return entity as Json; } - else if (entity is String) { return From((String)entity, lower); } + else if (entity is String) { return From((String)entity); } else if (entity is IDictionary) { return From(entity as IDictionary, (bool)lower); } else if (entity is IList) { return From(entity as IList, (bool)lower); } @@ -1395,13 +1392,13 @@ namespace Apewer if (checker != null && !checker.WithPropertyInJson(entity, property, value)) continue; // 处理 Type 对象。 - if (getter.ReturnType.Equals(typeof(Type)) && (previous.Count > 2)) + if (getter.ReturnType.Equals(typeof(Type)) && (previous.Length > 2)) { value = ((Type)value).FullName; } // 处理 Assembly 对象。 - if (getter.ReturnType.Equals(typeof(Assembly)) && (previous.Count > 2)) + if (getter.ReturnType.Equals(typeof(Assembly)) && (previous.Length > 2)) { value = ((Assembly)value).FullName; } @@ -1461,8 +1458,8 @@ namespace Apewer return (T)entity; } - /// 将 Json 数组填充到列表,失败时返回 NULL 值。 - internal static List Array(Json json, bool ignoreCase = true, string ignoreCharacters = null, bool force = false) where T : class, new() + /// 将 Json 填充到数组列表,失败时返回 NULL 值。 + internal static T[] Array(Json json, bool ignoreCase = true, string ignoreCharacters = null, bool force = false) where T : class, new() { if (json == null) return null; if (json._jtoken == null) return null; @@ -1470,7 +1467,7 @@ namespace Apewer var list = new List(); Array(list, json, ignoreCase, ignoreCharacters, force); - return list; + return list.ToArray(); } /// @@ -1480,7 +1477,7 @@ namespace Apewer if (json.TokenType != JTokenType.Object) return; var jps = json.GetProperties(); - if (jps.Count < 1) return; + if (jps.Length < 1) return; var etype = entity.GetType(); var eps = etype.GetProperties(); @@ -1998,6 +1995,7 @@ namespace Apewer { if (type == null) return false; + if (type.Equals(typeof(object))) return false; var sas = type.GetCustomAttributes(typeof(SerializableAttribute), inherit); if (sas != null && sas.Length > 0) return true; @@ -2055,7 +2053,7 @@ namespace Apewer /// 将要反序列化的 JSON 字符串。 /// 发生错误时返回 NULL 值,设置为 FALSE 时返回空 List<> 对象。 /// - public static List DeserializeList(string json, bool returnNullOnError = false) where T : class + public static T[] DeserializeArray(string json, bool returnNullOnError = false) where T : class { try { @@ -2065,14 +2063,12 @@ namespace Apewer { using (var jtr = new JsonTextReader(sr)) { - @object = serializer.Deserialize(jtr, typeof(List)); + @object = serializer.Deserialize(jtr, typeof(T[])); } } - var list = @object as List; - if (list == null) list = new List(); - return list; + return (@object as T[]) ?? new T[0]; } - catch { return returnNullOnError ? null : new List(); } + catch { return returnNullOnError ? null : new T[0]; } } #endif diff --git a/Apewer/Network/HttpClient.cs b/Apewer/Network/HttpClient.cs index bb0720a..eab8c3d 100644 --- a/Apewer/Network/HttpClient.cs +++ b/Apewer/Network/HttpClient.cs @@ -16,7 +16,7 @@ namespace Apewer.Network public class HttpClient { - private string _key = TextUtility.NewGuid(); + private string _key = TextUtility.Guid(); internal bool _locked = false; private TextSet _properties = new TextSet(true); diff --git a/Apewer/Result.cs b/Apewer/Result.cs index a73d108..02eda65 100644 --- a/Apewer/Result.cs +++ b/Apewer/Result.cs @@ -87,10 +87,10 @@ namespace Apewer public Result(int code, string message = null) : base(code, message) { } /// 创建实例:Value = Default。 - public Result(Exception exception, int code = 0) : base(Stringify(exception), code) { } + public Result(Exception exception, int code = 0) : base(RuntimeUtility.Message(exception), code) { } /// 创建实例:Value = Default。 - public Result(int code, Exception exception = null) : base(code, Stringify(exception)) { } + public Result(int code, Exception exception = null) : base(code, RuntimeUtility.Message(exception)) { } private void Set(T value) { @@ -98,18 +98,6 @@ namespace Apewer _has = typeof(T).IsValueType ? true : (value != null); } - private static string Stringify(Exception exception) - { - if (exception == null) return null; - var message = exception.Message; - if (string.IsNullOrEmpty(message)) - { - var type = exception.GetType().FullName; - message = $"包含了无效消息的 {type}。"; - } - return message; - } - #region 运算符。 /// 含有实体对象。 diff --git a/Apewer/RuntimeUtility.cs b/Apewer/RuntimeUtility.cs index 45f667d..6010c3f 100644 --- a/Apewer/RuntimeUtility.cs +++ b/Apewer/RuntimeUtility.cs @@ -351,7 +351,7 @@ namespace Apewer // 忽略 System.Object。 var quantum = typeof(object); if (@base.Equals(quantum)) return true; - if (child.Equals(quantum)) return true; + if (child.Equals(quantum)) return false; // 循环判断基类。 var current = child; @@ -368,12 +368,12 @@ namespace Apewer } /// - public static Type[] GetTypes(Assembly assembly, bool onlyExperted = false) + public static Type[] GetTypes(Assembly assembly, bool onlyExported = false) { if (assembly == null) return null; try { - return onlyExperted ? assembly.GetExportedTypes() : assembly.GetTypes(); + return onlyExported ? assembly.GetExportedTypes() : assembly.GetTypes(); } catch { } return new Type[0]; @@ -421,6 +421,22 @@ namespace Apewer return false; } + /// 在程序集中枚举派生类型,可自定义检查器。 + public static Type[] DerivedTypes(Type baseType, Assembly assembly, Func checker) + { + if (baseType == null) return new Type[0]; + if (assembly == null) return new Type[0]; + var types = GetTypes(assembly); + var list = new List(types.Length); + foreach (var type in types) + { + if (!IsInherits(type, baseType)) continue; + if (checker != null && !checker(type)) continue; + list.Add(type); + } + return list.ToArray(); + } + #endregion #region Collect & Dispose @@ -763,6 +779,29 @@ namespace Apewer #endregion + #region Exception + + internal static string Message(Exception ex) + { + if (ex == null) return null; + try + { + var message = ex.Message; + if (!string.IsNullOrEmpty(message)) return message; + + var typeName = ex.GetType().FullName; + message = $"异常 <{typeName}> 包含空消息。"; + return message; + } + catch + { + var typeName = ex.GetType().FullName; + return $"获取 <{typeName}> 的消息时再次发生了异常。"; + } + } + + #endregion + } } diff --git a/Apewer/Source/ColumnAttribute.cs b/Apewer/Source/ColumnAttribute.cs index b5d5cab..c648e4d 100644 --- a/Apewer/Source/ColumnAttribute.cs +++ b/Apewer/Source/ColumnAttribute.cs @@ -7,35 +7,37 @@ using System.Text; namespace Apewer.Source { - /// 数据库中的列,类型默认为 NVarChar(191),错误类型将修正为 NText。 + /// 数据库中的列,类型默认为 NVarChar(191),错误类型将修正为默认类型。 + /// 注意:当一个数据模型中存在多个相同的 Field 时,将只有第一个被保留。 [Serializable] [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class ColumnAttribute : Attribute { private PropertyInfo _property = null; + internal string PropertyName = null; private string _field = ""; private int _length = 0; private ColumnType _type; private bool _independent = false; + private bool _valid = true; - /// - private void Init(string field, ColumnType type, int length, bool underline) + private void Init(string field, ColumnType type, int length) { - _field = string.IsNullOrEmpty(field) ? "" : TableStructure.RestrictName(field, underline); + if (string.IsNullOrEmpty(field)) field = TableStructure.RestrictName(field, string.IsNullOrEmpty(field)); + _field = string.IsNullOrEmpty(field) ? "" : TableStructure.RestrictName(field, string.IsNullOrEmpty(field)); _type = type; switch (type) { case ColumnType.VarChar: case ColumnType.NVarChar: - if (length < 1) throw new ArgumentException("最大长度无效。"); - _length = length; + _length = length < 1 ? 191 : length; break; - case ColumnType.VarChar255: - case ColumnType.NVarChar255: - _length = 255; + case ColumnType.VarChar191: + case ColumnType.NVarChar191: + _length = 191; break; default: _length = length; @@ -44,48 +46,135 @@ namespace Apewer.Source } /// 使用自动的列名称。当类型为 VarChar 或 NVarChar 时必须指定长度。 - /// - public ColumnAttribute(ColumnType type = ColumnType.NVarChar, int length = 191) => Init(null, type, length, true); + /// + public ColumnAttribute(ColumnType type = ColumnType.NVarChar191, int length = 191) => Init(null, type, length); /// 使用指定的列名称。当类型为 VarChar 或 NVarChar 时必须指定长度。 - /// - public ColumnAttribute(string field, ColumnType type = ColumnType.NVarChar, int length = 191) => Init(field, type, length, false); - - internal ColumnAttribute(string field, ColumnType type, int length, bool underline) => Init(field, type, length, underline); - - /// 属性。 - public PropertyInfo Property - { - get => _property; - internal set => _property = value; - } + /// + public ColumnAttribute(string field, ColumnType type = ColumnType.NVarChar191, int length = 191) => Init(field, type, length); /// 字段名。 - public string Field - { - get => _field; - set => _field = value; - } + public string Field { get => _field; } /// 指定字段的最大长度。 - public int Length - { - get => _length; - set => _length = value; - } + public int Length { get => _length; } /// 字段类型。 - public ColumnType Type + public ColumnType Type { get => _type; } + + #region 附加 + + /// 此特性有效。 + public bool Valid { get => _valid; } + + /// Independent 特性。 + public bool Independent { get => _independent; } + + /// 使用此特性的属性。 + public PropertyInfo Property { get => _property; } + + #endregion + + /// 解析列特性。 + /// 注意:此方法不再抛出异常,当不存在正确的列特性时将返回 NULL 值 + public static ColumnAttribute Parse(Type type, PropertyInfo property, TableAttribute ta) { - get => _type; - set => _type = value; + if (type == null || property == null || ta == null) return null; + + // 属性带有 Independent 特性。 + if (property.Contains()) return null; + + // 检查 ColumnAttribute。 + ColumnAttribute ca; + { + var cas = property.GetCustomAttributes(typeof(ColumnAttribute), false); + if (cas.LongLength < 1L) + { + if (!ta.AllProperties) return null; + ca = new ColumnAttribute(); + } + else ca = (ColumnAttribute)cas[0]; + } + + // 检查属性方法。 + var getter = property.GetGetMethod(false); + var setter = property.GetSetMethod(false); + if (getter == null || getter.IsStatic) return null; + if (setter == null || setter.IsStatic) return null; + + // 检查列名称。 + if (TextUtility.IsBlank(ca.Field)) ca._field = "_" + property.Name; + + // 类型兼容。 + var pt = property.PropertyType; + if (pt.Equals(typeof(byte[]).FullName)) ca._type = ColumnType.Bytes; + else if (pt.Equals(typeof(Byte))) ca._type = ColumnType.Integer; + else if (pt.Equals(typeof(SByte))) ca._type = ColumnType.Integer; + else if (pt.Equals(typeof(Int16))) ca._type = ColumnType.Integer; + else if (pt.Equals(typeof(UInt16))) ca._type = ColumnType.Integer; + else if (pt.Equals(typeof(Int32))) ca._type = ColumnType.Integer; + else if (pt.Equals(typeof(UInt32))) ca._type = ColumnType.Integer; + else if (pt.Equals(typeof(Int64))) ca._type = ColumnType.Integer; + else if (pt.Equals(typeof(Single))) ca._type = ColumnType.Float; + else if (pt.Equals(typeof(Double))) ca._type = ColumnType.Float; + else if (pt.Equals(typeof(Decimal))) ca._type = ColumnType.Float; + else if (pt.Equals(typeof(DateTime))) ca._type = ColumnType.DateTime; + else if (pt.Equals(typeof(String))) + { + switch (ca.Type) + { + case ColumnType.Bytes: + case ColumnType.Integer: + case ColumnType.Float: + case ColumnType.DateTime: + //throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 的类型不受支持。")); + ca._type = ColumnType.NVarChar; + ca._length = 191; + break; + } + } + else + { + ca._type = ColumnType.NVarChar191; + ca._length = 191; + } + + ca._property = property; + ca.PropertyName = property.Name; + if (ca.PropertyName == "Key" || ca.PropertyName == "Flag") ca._independent = true; + + return ca; } - /// Independent 特性。 - public bool Independent + /// 对列特性排序,Key 和 Flag 将始终排在前部。 + public static ColumnAttribute[] Sort(ColumnAttribute[] columns, bool sort = false) { - get => _independent; - internal set => _independent = value; + var total = columns.Length; + var key = null as ColumnAttribute; + var flag = null as ColumnAttribute; + var temp = new List(total); + for (var i = 0; i < total; i++) + { + var ca = columns[i]; + if (ca == null) continue; + var pn = ca.Property.Name; + if (pn == "Key") key = ca; + else if (pn == "Flag") flag = ca; + else temp.Add(ca); + } + if (sort && temp.Count > 0) temp.Sort((a, b) => a._field.CompareTo(b._field)); + + if (key == null && flag == null) return temp.ToArray(); + + total = 0; + if (key != null) total += 1; + if (flag != null) total += 1; + total += temp.Count; + var sorted = new List(total); + if (key != null) sorted.Add(key); + if (flag != null) sorted.Add(flag); + sorted.AddRange(temp); + return sorted.ToArray(); } } diff --git a/Apewer/Source/ColumnType.cs b/Apewer/Source/ColumnType.cs index 867de8c..12ee808 100644 --- a/Apewer/Source/ColumnType.cs +++ b/Apewer/Source/ColumnType.cs @@ -26,7 +26,7 @@ namespace Apewer.Source VarChar, /// 长可变长度的字符串(System.String),最多 255 个字符。 - VarChar255, + VarChar191, /// 可变长度的字符串(System.String)。 VarCharMax, @@ -38,7 +38,7 @@ namespace Apewer.Source NVarChar, /// 可变长度的字符串(System.String),最多 255 个字符。 - NVarChar255, + NVarChar191, /// 可变长度的字符串(System.String)。 NVarCharMax, diff --git a/Apewer/Source/Example.cs b/Apewer/Source/Example.cs index cff80dd..cbdc76a 100644 --- a/Apewer/Source/Example.cs +++ b/Apewer/Source/Example.cs @@ -9,37 +9,23 @@ namespace Apewer.Source public class Example { - private static IExecute CreateExecuteError(string error) - { - var execute = new Execute(); - execute.Error = error; - return execute; - } - - private static IQuery CreateQueryError(string error) - { - var query = new Query(); - query.Error = error; - return query; - } - /// - public static IExecute InvalidExecuteConnection => CreateExecuteError("连接无效。"); + public static IExecute InvalidExecuteConnection => new Execute(false, "连接无效。"); /// - public static IExecute InvalidExecuteStatement => CreateExecuteError("语句无效。"); + public static IExecute InvalidExecuteStatement => new Execute(false, "语句无效。"); /// - public static IExecute InvalidExecuteParameters => CreateExecuteError("参数无效。"); + public static IExecute InvalidExecuteParameters => new Execute(false, "参数无效。"); /// - public static IQuery InvalidQueryConnection => CreateQueryError("连接无效。"); + public static IQuery InvalidQueryConnection => new Query(false, "连接无效。"); /// - public static IQuery InvalidQueryStatement => CreateQueryError("语句无效。"); + public static IQuery InvalidQueryStatement => new Query(false, "语句无效。"); /// - public static IQuery InvalidQueryParameters => CreateQueryError("参数无效。"); + public static IQuery InvalidQueryParameters => new Query(false, "参数无效。"); } diff --git a/Apewer/Source/Execute.cs b/Apewer/Source/Execute.cs index 31f54b0..30a6290 100644 --- a/Apewer/Source/Execute.cs +++ b/Apewer/Source/Execute.cs @@ -5,71 +5,57 @@ namespace Apewer.Source { /// 数据库引擎的执行结果。 - public class Execute : IExecute + public class Execute : IExecute, IToJson { private bool _success = false; - private int _rows = 0; - private string _error = ""; private string _message = ""; - private Exception _exception = null; + private int _rows = 0; /// 语句执行成功。 - public bool Success - { - get { return _success; } - set { _success = value; } - } + public bool Success { get => _success; } + + /// 受影响的行数。 + public int Rows { get => _rows; } - /// 执行失败时的异常。 - public Exception Exception + /// 消息。 + public string Message { get => _message; } + + /// 创建实例。 + public Execute(bool success, string message) { - get { return _exception; } - set { _exception = value; } + _success = false; + _message = message; } - /// 受影响的行数。 - public int Rows + /// 创建实例。 + public Execute(bool success, int rows) { - get { return _rows; } - set { _rows = value; } + _success = success; + _rows = rows; } - /// 错误信息。 - public string Error + /// 创建实例,Exception 为 NULL 时候成功,非 NULL 时为失败。 + public Execute(Exception exception) { - get - { - if (!string.IsNullOrEmpty(_error)) - { - return _error; - } - else - { - if (_exception != null) - { - try - { - return _exception.Message; - } - catch - { - return _exception.GetType().FullName; - } - } - } - return ""; - } - set { _error = value ?? ""; } + _success = exception == null; + _message = RuntimeUtility.Message(exception); } - /// 消息。 - public string Message + #region IToJson + + /// 转换为 Json 对象。 + public Json ToJson() { - get { return _message ?? ""; } - set { _message = value ?? ""; } + var jsonObject = Json.NewObject(); + jsonObject.SetProperty("success", _success); + jsonObject.SetProperty("message", _message); + jsonObject.SetProperty("rows", _rows); + return jsonObject; } + #endregion + } } diff --git a/Apewer/Source/HttpRecord.cs b/Apewer/Source/HttpRecord.cs index 431cb23..611ff09 100644 --- a/Apewer/Source/HttpRecord.cs +++ b/Apewer/Source/HttpRecord.cs @@ -16,7 +16,7 @@ namespace Apewer.Source private TextSet _ts = new TextSet(true); /// NVarChar255 - [Column("_url_md5", ColumnType.NVarChar255)] + [Column("_url_md5", ColumnType.NVarChar191)] public string UrlMd5 { get { return _ts["UrlMd5"]; } set { _ts["UrlMd5"] = value; } } /// NText @@ -24,7 +24,7 @@ namespace Apewer.Source public string UrlText { get { return _ts["UrlText"]; } set { _ts["UrlText"] = value; } } /// NVarChar255 - [Column("_http_code", ColumnType.NVarChar255)] + [Column("_http_code", ColumnType.NVarChar191)] public string HttpCode { get { return _ts["HttpCode"]; } set { _ts["HttpCode"] = value; } } /// NText @@ -36,15 +36,15 @@ namespace Apewer.Source public string HttpException { get { return _ts["HttpException"]; } set { _ts["HttpException"] = value; } } /// NVarChar255 - [Column("_client_ip", ColumnType.NVarChar255)] + [Column("_client_ip", ColumnType.NVarChar191)] public string ClientIp { get { return _ts["ClientIp"]; } set { _ts["ClientIp"] = value; } } /// NVarChar255 - [Column("_request_beginning", ColumnType.NVarChar255)] + [Column("_request_beginning", ColumnType.NVarChar191)] public string RequestBeginning { get { return _ts["RequestBeginning"]; } set { _ts["RequestBeginning"] = value; } } /// NVarChar255 - [Column("_request_ending", ColumnType.NVarChar255)] + [Column("_request_ending", ColumnType.NVarChar191)] public string RequestEnding { get { return _ts["RequestEnding"]; } set { _ts["RequestEnding"] = value; } } /// NText @@ -60,11 +60,11 @@ namespace Apewer.Source public string RequestText { get { return _ts["RequestText"]; } set { _ts["RequestText"] = value; } } /// NVarChar255 - [Column("_response_beginning", ColumnType.NVarChar255)] + [Column("_response_beginning", ColumnType.NVarChar191)] public string ResponseBeginning { get { return _ts["ResponseBeginning"]; } set { _ts["ResponseBeginning"] = value; } } /// NVarChar255 - [Column("_response_ending", ColumnType.NVarChar255)] + [Column("_response_ending", ColumnType.NVarChar191)] public string ResponseEnding { get { return _ts["ResponseEnding"]; } set { _ts["ResponseEnding"] = value; } } /// NText diff --git a/Apewer/Source/IDatabaseBase.cs b/Apewer/Source/IDatabaseBase.cs deleted file mode 100644 index efe4ef5..0000000 --- a/Apewer/Source/IDatabaseBase.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Apewer.Source -{ - - /// 数据库引擎接口。 - public interface IDatabaseBase : IDisposable - { - - /// 获取或设置日志记录器。 - Logger Logger { get; set; } - - /// 数据库当前在线,表示连接可用。 - bool Online { get; } - - /// 连接数据库,若未连接则尝试连接,获取连接成功的状态。 - bool Connect(); - - } - -} diff --git a/Apewer/Source/IDatabaseExecute.cs b/Apewer/Source/IDatabaseExecute.cs deleted file mode 100644 index 69c2285..0000000 --- a/Apewer/Source/IDatabaseExecute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Text; - -namespace Apewer.Source -{ - - /// 数据库引擎接口。 - public interface IDatabaseExecute - { - - /// 执行。 - IExecute Execute(string statement); - - /// 执行。 - IExecute Execute(string statement, IEnumerable parameters); - - } - -} diff --git a/Apewer/Source/IDatabaseQuery.cs b/Apewer/Source/IDatabaseQuery.cs deleted file mode 100644 index 88732af..0000000 --- a/Apewer/Source/IDatabaseQuery.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Text; - -namespace Apewer.Source -{ - - /// 数据库引擎接口。 - public interface IDatabaseQuery - { - - /// 查询。 - IQuery Query(string statement); - - /// 查询。 - IQuery Query(string statement, IEnumerable parameters); - - } - -} diff --git a/Apewer/Source/IDatabase.cs b/Apewer/Source/IDbClient.cs similarity index 59% rename from Apewer/Source/IDatabase.cs rename to Apewer/Source/IDbClient.cs index bf65cd1..1734ed4 100644 --- a/Apewer/Source/IDatabase.cs +++ b/Apewer/Source/IDbClient.cs @@ -7,6 +7,6 @@ namespace Apewer.Source { /// 数据库引擎接口。 - public interface IDatabase : IDisposable, IDatabaseBase, IDatabaseQuery, IDatabaseExecute, IDatabaseOrm { } + public interface IDbClient : IDisposable, IDbClientBase, IDbClientAdo, IDbClientOrm { } } diff --git a/Apewer/Source/IDbClientAdo.cs b/Apewer/Source/IDbClientAdo.cs new file mode 100644 index 0000000..83e5844 --- /dev/null +++ b/Apewer/Source/IDbClientAdo.cs @@ -0,0 +1,72 @@ +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; } + + /// 连接数据库,若未连接则尝试连接,获取连接成功的状态。 + bool 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 new file mode 100644 index 0000000..2f15b4f --- /dev/null +++ b/Apewer/Source/IDbClientBase.cs @@ -0,0 +1,17 @@ +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/IDatabaseOrm.cs b/Apewer/Source/IDbClientOrm.cs similarity index 87% rename from Apewer/Source/IDatabaseOrm.cs rename to Apewer/Source/IDbClientOrm.cs index dad3282..674214b 100644 --- a/Apewer/Source/IDatabaseOrm.cs +++ b/Apewer/Source/IDbClientOrm.cs @@ -6,7 +6,7 @@ namespace Apewer.Source { /// 数据库引擎支持 ORM 访问。 - public interface IDatabaseOrm + public interface IDbClientOrm { /// 初始化指定类型,以创建表或增加字段。 @@ -31,11 +31,11 @@ namespace Apewer.Source /// 获取指定类型的主键,按 Flag 属性筛选。 /// 要查询的类型。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result> Keys(Type model, long flag = 0); + public Result Keys(Type model, long flag = 0); /// 获取指定类型的主键,按 Flag 属性筛选。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result> Keys(long flag = 0) where T : class, IRecord, new(); + public Result Keys(long flag = 0) where T : class, IRecord, new(); /// 获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。 /// 目标记录的类型。 @@ -51,20 +51,20 @@ namespace Apewer.Source /// 使用指定语句查询,获取查询结果。 /// 目标记录的类型。 /// 要执行的 SQL 语句。 - public Result> Query(Type model, string sql); + public Result Query(Type model, string sql); /// 使用指定语句查询,获取查询结果。 /// 要执行的 SQL 语句。 - public Result> Query(string sql) where T : class, IRecord, new(); + public Result Query(string sql) where T : class, IRecord, new(); /// 查询所有记录。 /// 目标记录的类型。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result> Query(Type model, long flag = 0); + public Result Query(Type model, long flag = 0); /// 查询所有记录。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result> Query(long flag = 0) where T : class, IRecord, new(); + public Result Query(long flag = 0) where T : class, IRecord, new(); } diff --git a/Apewer/Source/IExecute.cs b/Apewer/Source/IExecute.cs index 06b5a40..d564431 100644 --- a/Apewer/Source/IExecute.cs +++ b/Apewer/Source/IExecute.cs @@ -15,15 +15,9 @@ namespace Apewer.Source /// 受影响的行数。 int Rows { get; } - /// 错误消息。 - string Error { get; } - /// 消息。 string Message { get; } - /// 执行失败时的异常。 - Exception Exception { get; } - } } diff --git a/Apewer/Source/IQuery.cs b/Apewer/Source/IQuery.cs index bf656f6..6cb0978 100644 --- a/Apewer/Source/IQuery.cs +++ b/Apewer/Source/IQuery.cs @@ -15,24 +15,12 @@ namespace Apewer.Source /// 语句执行成功。 bool Success { get; } - /// 错误信息。 - string Error { get; } - /// 消息。 string Message { get; } - /// 语句执行失败时的 Exception 对象。 - Exception Exception { get; } - - /// 所有结果表。 - List Tables { get; } - /// 获取默认结果表。如果设置默认结果表,会丢失设置前的所有结果表。 DataTable Table { get; } - /// 结果集为空。 - bool Empty { get; } - /// 默认表中的数据总行数。 int Rows { get; } @@ -66,31 +54,6 @@ namespace Apewer.Source #endregion - #region 以文本获取结果集中的内容。 - - /// 获取默认表中第 0 行、第 0 列的单元格内容。 - string Text(); - - /// 获取默认表中指定行中第 0 列的内容。 - /// 行索引,从 0 开始。 - string Text(int rowIndex); - - /// 获取默认表中第 0 行指定列的内容。 - /// 列名称。 - string Text(string columnName); - - /// 获取默认表中指定单元格的内容。 - /// 行索引,从 0 开始。 - /// 列索引,从 0 开始。 - string Text(int rowIndex, int columnIndex); - - /// 获取默认表中指定单元的内容。 - /// 行索引,从 0 开始。 - /// 列名称。 - string Text(int rowIndex, string columnName); - - #endregion - } } diff --git a/Apewer/Source/IRecord.cs b/Apewer/Source/IRecord.cs index e81715a..9c5cb0e 100644 --- a/Apewer/Source/IRecord.cs +++ b/Apewer/Source/IRecord.cs @@ -6,15 +6,21 @@ namespace Apewer.Source { /// 数据库记录通用字段模型。 + /// 带有 Independent 特性的模型不包含此接口声明的属性。 public interface IRecord { /// 记录唯一键,一般使用 GUID 的字符串形式,字段长度不应超过 255 个字符。 + /// 带有 Independent 特性的模型不包含此属性。 string Key { get; set; } /// 记录的标记,区分记录的状态。 + /// 带有 Independent 特性的模型不包含此属性。 long Flag { get; set; } + /// 重置 Key 属性的值。 + void ResetKey(); + } } diff --git a/Apewer/Source/OrmHelper.cs b/Apewer/Source/OrmHelper.cs index 46960ad..6cd59e8 100644 --- a/Apewer/Source/OrmHelper.cs +++ b/Apewer/Source/OrmHelper.cs @@ -13,15 +13,17 @@ namespace Apewer.Source #region As - private static List As(List input) where T : IRecord + private static T[] As(IRecord[] input) where T : IRecord { if (input == null) return null; - var output = new List(input.Count); - foreach (var record in input) + var count = input.Length; + var output = new T[count]; + for (var i = 0; i < count; i++) { + var record = input[i]; if (record == null) continue; var t = (T)record; - output.Add(t); + output[i] = t; } return output; } @@ -34,17 +36,14 @@ namespace Apewer.Source return new Result(value); } - private static Result> As(Result> input) where T : class, IRecord, new() + private static Result As(Result input) where T : class, IRecord, new() { if (input == null) return null; - if (!input.HasValue) return new Result>(input.Code, input.Message); - var list = new List(input.Value.Count); - foreach (var inputItem in input.Value) - { - var value = inputItem as T; - list.Add(value); - } - return new Result>(list); + if (!input.HasValue) return new Result(input.Code, input.Message); + var count = input.Value.Length; + var output = new T[count]; + for (var i = 0; i < count; i++) output[i] = input.Value[i] as T; + return new Result(output); } #endregion @@ -52,81 +51,84 @@ namespace Apewer.Source #region IQuery -> IRecord /// 读取所有行,生成列表。 - public static List Fill(IQuery query) where T : class, IRecord, new() => As(Fill(query, typeof(T))); + public static T[] Fill(IQuery query) where T : class, IRecord, new() => As(Fill(query, typeof(T))); - /// 读取所有行填充到 T,组成 List<T>。 - /// - public static List Fill(IQuery query, Type model) + /// 读取所有行填充到 T,组成 T[]。 + public static IRecord[] Fill(IQuery query, Type model) { - if (query == null) return new List(); - if (model == null) return new List(); + if (query == null) return new IRecord[0]; + if (model == null) return new IRecord[0]; - var list = new List(query.Rows); - var ts = TableStructure.ParseModel(model); - for (int r = 0; r < query.Rows; r++) - { - var record = Row(query, r, model, ts); - if (record != null) list.Add(record); - } - list.Capacity = list.Count; - return list; + var ts = TableStructure.Parse(model); + var output = new IRecord[query.Rows]; + for (int r = 0; r < query.Rows; r++) output[r] = Row(query, r, model, ts); + return output; } /// 获取指定列的所有值,无效值不加入结果。 - /// - public static List Column(IQuery query, Func filler) + public static T[] Column(IQuery query, Func filler) { - if (query == null) throw new ArgumentNullException(nameof(query)); - if (filler == null) throw new ArgumentNullException(nameof(filler)); + if (query == null || filler == null) return new T[0]; var rows = query.Rows; - var list = new List(rows); - if (rows > 0) + var output = new T[rows]; + var added = 0; + for (int r = 0; r < rows; r++) { - list.Capacity = rows; - var added = 0; - for (int r = 0; r < rows; r++) + var value = filler(r); + if (value == null) continue; + if (value is string str) { - var value = filler(r); - if (value == null) continue; - if (value is string) - { - var valueString = value as string; - if (string.IsNullOrEmpty(valueString)) continue; - } - list.Add(value); - added++; + if (str == "") continue; } - list.Capacity = added; + output[added] = value; + added++; } - return list; + + if (added < 1) return new T[0]; + if (added == rows) return output; + var output2 = new T[added]; + Array.Copy(output, output2, added); + return output2; } - /// 填充指定行为记录实体。 + /// 将 Query 的行,填充到模型实体。 /// 填充失败时返回 NULL 值。 - /// - /// - /// public static IRecord Row(IQuery query, int rowIndex, Type model, TableStructure structure) { - if (query == null) throw new ArgumentNullException(nameof(query)); - if (model == null) throw new ArgumentNullException(nameof(model)); - if (structure == null) throw new ArgumentNullException(nameof(structure)); - if (rowIndex < 0 || rowIndex >= query.Rows) throw new ArgumentOutOfRangeException(nameof(rowIndex)); - var record = Activator.CreateInstance(model); - var properties = model.GetProperties(); + // 检查参数。 + if (query == null || model == null || structure == null) return null; + if (rowIndex < 0 || rowIndex >= query.Rows) return null; + if (!RuntimeUtility.CanNew(model)) return null; + + // 变量别名。 var ts = structure; var r = rowIndex; + var columns = ts.Columns; + + // 检查模型的属性,按属性从表中取相应的列。 + var record = Activator.CreateInstance(model); + var properties = model.GetProperties(); foreach (var property in properties) { - if (ts.Columns.ContainsKey(property.Name) == false) continue; - + // 必须有 setter 访问器。 var setter = property.GetSetMethod(); if (setter == null) continue; - var attribute = ts.Columns[property.Name]; - var pt = property.PropertyType; + // 在表结构中检查,是否包含此属性,并获取 ColumnAttribute。 + var attribute = null as ColumnAttribute; + for (var j = 0; j < columns.Length; j++) + { + if (columns[j].PropertyName == property.Name) + { + attribute = columns[j]; + break; + } + } + if (attribute == null) continue; + // 根据属性类型设置值。 + var pt = property.PropertyType; if (pt.Equals(typeof(object)) || pt.Equals(typeof(Nullable))) { setter.Invoke(record, new object[] { query.Value(r, attribute.Field) }); @@ -197,8 +199,7 @@ namespace Apewer.Source catch { } } } - var iRecord = record as IRecord; - return iRecord; + return record as IRecord; } #endregion @@ -209,23 +210,27 @@ namespace Apewer.Source /// 数据库对象。 /// 记录模型。 /// SQL 语句。 - public static Result> Query(IDatabaseQuery database, Type model, string sql) + public static Result Query(IDbClientAdo database, Type model, string sql) { - if (database == null) return new Result>("数据库无效。"); - if (model == null) return new Result>("模型类型无效。"); - if (string.IsNullOrEmpty(sql)) return new Result>("SQL 语句无效。"); + 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) as Query) { - if (query == null) return new Result>("查询实例无效。"); - if (query.Exception != null) return new Result>(query.Exception); + 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 list = Fill(query, model); - return new Result>(list); + var array = Fill(query, model); + return new Result(array); } catch (Exception ex) { - return new Result>(ex); + return new Result(ex); } } } @@ -234,24 +239,24 @@ namespace Apewer.Source /// 记录模型。 /// 数据库对象。 /// SQL 语句。 - public static Result> Query(IDatabaseQuery database, string sql) where T : class, IRecord, new() => As(Query(database, typeof(T), sql)); + public static Result Query(IDbClientAdo database, string sql) where T : class, IRecord, new() => As(Query(database, typeof(T), sql)); /// 查询记录。 /// 数据库对象。 /// 记录模型。 /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result> Query(IDatabaseQuery database, Type model, Func sqlGetter) + public static Result Query(IDbClientAdo database, Type model, Func sqlGetter) { - if (sqlGetter == null) return new Result>("SQL 语句获取函数无效。"); + if (sqlGetter == null) return new Result("SQL 语句获取函数无效。"); try { - var tableName = TableStructure.ParseModel(model).Table; - if (string.IsNullOrEmpty(tableName)) return new Result>("表名无效。"); + var tableName = TableStructure.Parse(model).Name; + if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。"); return Query(database, model, sqlGetter(tableName)); } catch (Exception ex) { - return new Result>(ex); + return new Result(ex); } } @@ -259,14 +264,14 @@ namespace Apewer.Source /// 记录模型。 /// 数据库对象。 /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result> Query(IDatabaseQuery database, Func sqlGetter) where T : class, IRecord, new() => As(Query(database, typeof(T), sqlGetter)); + public static Result Query(IDbClientAdo database, Func sqlGetter) where T : class, IRecord, new() => As(Query(database, typeof(T), sqlGetter)); /// 获取具有指定主键的记录。 /// 数据库对象。 /// 记录模型。 /// 主键。 /// 生成 SQL 语句的函数,传入参数为表名和主键值。 - public static Result Get(IDatabaseQuery database, Type model, string key, Func sqlGetter) + public static Result Get(IDbClientAdo database, Type model, string key, Func sqlGetter) { if (sqlGetter == null) return new Result("SQL 语句获取函数无效。"); @@ -277,8 +282,8 @@ namespace Apewer.Source var record = null as IRecord; try { - var ts = TableStructure.ParseModel(model); - var tableName = ts.Table; + var ts = TableStructure.Parse(model); + var tableName = ts.Name; if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。"); var sql = sqlGetter(tableName, safetyKey); @@ -301,30 +306,30 @@ namespace Apewer.Source /// 数据库对象。 /// 主键。 /// 生成 SQL 语句的函数,传入参数为表名和主键值。 - public static Result Get(IDatabaseQuery database, string key, Func sqlGetter) where T : class, IRecord, new() => As(Get(database, typeof(T), key, sqlGetter)); + public static Result Get(IDbClientAdo database, string key, Func sqlGetter) where T : class, IRecord, new() => As(Get(database, typeof(T), key, sqlGetter)); /// 获取主键。 /// 数据库对象。 /// 记录模型。 /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result> Keys(IDatabaseQuery database, Type model, Func sqlGetter) + public static Result Keys(IDbClientAdo database, Type model, Func sqlGetter) { - if (database == null) return new Result>("数据库无效。"); - if (model == null) return new Result>("模型类型无效。"); - if (sqlGetter == null) return new Result>("SQL 语句获取函数无效。"); + 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.ParseModel(model); + tableStructure = TableStructure.Parse(model); } catch (Exception ex) { - return new Result>(ex); + return new Result(ex); } - var tableName = tableStructure.Table; - if (string.IsNullOrEmpty(tableName)) return new Result>("表名无效。"); + var tableName = tableStructure.Name; + if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。"); // var keyName = null as string; // foreach (var column in tableStructure.Columns) @@ -343,7 +348,7 @@ namespace Apewer.Source try { query = database.Query(sql); - if (query == null) return new Result>("查询实例无效。"); + if (query == null) return new Result("查询实例无效。"); var list = new List(query.Rows); for (var r = 0; r < query.Rows; r++) @@ -354,12 +359,13 @@ namespace Apewer.Source } query.Dispose(); list.Capacity = list.Count; - return new Result>(list); + var array = list.ToArray(); + return new Result(array); } catch (Exception ex) { RuntimeUtility.Dispose(query); - return new Result>(ex); + return new Result(ex); } } @@ -367,7 +373,7 @@ namespace Apewer.Source /// 记录模型。 /// 数据库对象。 /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result> Keys(IDatabaseQuery database, Func sqlGetter) where T : IRecord + public static Result Keys(IDbClientAdo database, Func sqlGetter) where T : IRecord { return Keys(database, typeof(T), sqlGetter); } diff --git a/Apewer/Source/Parameter.cs b/Apewer/Source/Parameter.cs index dedb481..4fb8303 100644 --- a/Apewer/Source/Parameter.cs +++ b/Apewer/Source/Parameter.cs @@ -10,24 +10,8 @@ namespace Apewer.Source public class Parameter { - private string _name; - /// 名称,不可设置位为空。 - /// - /// - public string Name - { - get - { - return _name; - } - set - { - if (value == null) throw new ArgumentNullException(); - if (value == "") throw new ArgumentException(); - _name = value; - } - } + public string Name { get; set; } /// 值。 public object Value { get; set; } @@ -35,7 +19,7 @@ namespace Apewer.Source /// 类型。 public ColumnType Type { get; set; } - /// 类型为 VarChar 时,可指定长度。 + /// 类型为 VarChar 时,指定长度。 public int Size { get; set; } /// 创建用于执行 SQL 语句的参数,名称不可设置位为空。 diff --git a/Apewer/Source/Query.cs b/Apewer/Source/Query.cs index d7e6ccf..aefd502 100644 --- a/Apewer/Source/Query.cs +++ b/Apewer/Source/Query.cs @@ -1,4 +1,5 @@ using Apewer.Internals; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Data; @@ -8,212 +9,74 @@ using static Apewer.TextUtility; namespace Apewer.Source { - /// 查询数据表。 - public class Query : IQuery, IDisposable + /// System.Data.DataTable 装箱查询。 + public class Query : IQuery, IDisposable, IToJson { private bool _disposed = false; private bool _success = false; - private string _error = ""; - private string _message = ""; - private Exception _exception = null; - private List _tables = new List(); + private string _message = null; + private DataTable _table = null; + private DataTable[] _tables = null; - #region Property - - /// 语句执行成功。 - public bool Success + /// 创建实例,默认状态为失败。 + public Query(bool success = false, string message = null) { - get - { - if (_disposed) return false; - return _success; - } - set - { - if (_disposed) return; - _success = value; - } - } - - /// 错误信息。 - public string Error - { - get - { - if (_disposed) return ""; - if (!string.IsNullOrEmpty(_error)) return _error; - if (_exception == null) return ""; - var error = ""; - try { error = _exception.Message; } finally { } - return error; - } - set - { - if (_disposed) return; - _error = value ?? ""; - } - } - - /// 消息。 - public string Message - { - get - { - if (_disposed) return ""; - return _message ?? ""; - } - set - { - if (_disposed) return; - _message = value ?? ""; - } + _success = false; + _message = message; } - /// 语句执行失败时的 Exception 对象。 - public Exception Exception + /// 创建实例,Exception 为 NULL 时成功,非 NULL 时失败。 + public Query(Exception exception) { - get { if (_disposed) return null; return _exception; } - set { if (_disposed) return; _exception = value; } + _success = exception == null; + _message = RuntimeUtility.Message(exception); } - /// 所有结果表。 - public List Tables + /// 创建实例,包装一个 DataTable 对象,数据表为 NULL 时失败,非 NULL 时成功。 + public Query(DataTable table) { - get { if (_disposed) return new List(); return _tables; } + _table = table; + _success = table != null; + _message = table == null ? "未获取有效的数据表。" : null; } - /// 获取默认结果表。如果设置默认结果表,会丢失设置前的所有结果表。 - public DataTable Table + /// 创建实例,包装一个 DataTable 对象。 + public Query(DataTable table, bool success, string message = null) { - get - { - if (_disposed) return null; - if (_tables.Count < 1) return null; - return _tables[0]; - } - set - { - if (_disposed) return; - Clear(); - if (_disposed) return; - _tables.Add(value); - } + _table = table; + _success = success; + _message = message; } - /// 所有表中不含内容行。 - public bool Empty + /// 创建实例,包装多个 DataTable 对象。 + public Query(DataTable[] tables, bool success = true, string message = null) { - get - { - if (_disposed) return true; - if (_tables.Count < 1) return true; - foreach (var table in _tables) - { - if (table == null) continue; - try - { - if (table.Rows.Count > 0) return false; - } - finally { } - } - return true; - } + _tables = tables; + _success = success; + _message = message; + if (tables != null && tables.Length > 0) _table = tables[0]; } - /// 默认表中的数据总行数。 - public int Rows - { - get - { - if (_disposed) return 0; - if (Table != null) return Table.Rows.Count; - else return 0; - } - } - - /// 默认表中的数据总列数。 - public int Columns - { - get - { - if (_disposed) return 0; - if (Table != null) return Table.Columns.Count; - else return 0; - } - } - - #endregion - - #region Text - - /// 获取默认表中第 0 行、第 0 列的单元格内容。 - public string Text() - { - if (_disposed) return Constant.EmptyString; - var value = Value(); - return Text(value); - } + #region Property - /// 获取默认表中指定行中第 0 列的内容。 - /// 行索引,从 0 开始。 - public string Text(int rowIndex) - { - if (_disposed) return Constant.EmptyString; - var value = Value(rowIndex); - return Text(value); - } + /// 语句执行成功。 + public bool Success { get => _success; } - /// 获取默认表中第 0 行指定列的内容。 - /// 列名称。 - public string Text(string columnName) - { - if (_disposed) return Constant.EmptyString; - var value = Value(columnName); - return Text(value); - } + /// 消息。 + public string Message { get => _message; } - /// 获取默认表中指定单元格的内容。 - /// 行索引,从 0 开始。 - /// 列索引,从 0 开始。 - public string Text(int rowIndex, int columnIndex) - { - if (_disposed) return Constant.EmptyString; - var value = Value(rowIndex, columnIndex); - return Text(value); - } + /// 所有结果表。 + public DataTable[] Tables { get => _tables; } - /// 获取默认表中指定单元的内容。 - /// 行索引,从 0 开始。 - /// 列名称。 - public string Text(int rowIndex, string columnName) - { - if (_disposed) return Constant.EmptyString; - var value = Value(rowIndex, columnName); - return Text(value); - } + /// 获取默认结果表。如果设置默认结果表,会丢失设置前的所有结果表。 + public DataTable Table { get => _table; } - /// 搜索默认表。 - /// 搜索条件:列名。 - /// 搜索条件:列值。 - /// 搜索结果。 - public string Text(string conditionColumn, string conditionValue, string resultColumn) - { - if (_disposed) return Constant.EmptyString; - var value = Value(conditionColumn, conditionValue, resultColumn); - return Text(value); - } + /// 默认表中的数据总行数。 + public int Rows { get => _table == null ? 0 : _table.Rows.Count; } - /// 搜索默认表。 - /// 搜索条件:列名。 - /// 搜索条件:列值。 - /// 搜索结果。 - public string Text(int conditionColumn, string conditionValue, int resultColumn) - { - if (_disposed) return Constant.EmptyString; - var value = Value(conditionColumn, conditionValue, resultColumn); - return Text(value); - } + /// 默认表中的数据总列数。 + public int Columns { get => _table == null ? 0 : _table.Columns.Count; } #endregion @@ -248,13 +111,13 @@ namespace Apewer.Source public object Value(int rowIndex, int columnIndex) { if (_disposed) return null; - if (Table != null) + if (_table != null) { - if (rowIndex >= 0 && rowIndex < Table.Rows.Count) + if (rowIndex >= 0 && rowIndex < _table.Rows.Count) { - if (columnIndex >= 0 && columnIndex < Table.Columns.Count) + if (columnIndex >= 0 && columnIndex < _table.Columns.Count) { - return Table.Rows[rowIndex][columnIndex]; + return _table.Rows[rowIndex][columnIndex]; } } } @@ -302,7 +165,7 @@ namespace Apewer.Source /// 搜索默认表。 /// 搜索条件:列名。 /// 搜索条件:列值。 - /// 搜索结果。 + /// 搜索结果的列名。 public object Value(int conditionColumn, string conditionValue, int resultColumn) { if (_disposed) return null; @@ -327,68 +190,28 @@ namespace Apewer.Source #region Method - /// 拆分表组,单独查询。 - public List Split() - { - var list = new List(); - if (_disposed) return list; - foreach (var table in _tables) - { - if (table == null) continue; - var query = new Query(); - query._success = true; - query._tables.Add(table); - list.Add(query); - } - return list; - } - - /// 添加数据表。 - public bool Add(DataTable tables) - { - if (_disposed) return false; - if (tables == null) return false; - _tables.Add(tables); - return true; - } - - /// 添加数据表。 - public int Add(IEnumerable tables) + /// 搜索默认表。 + /// 搜索条件:列名。 + /// 搜索条件:列值。 + /// 搜索结果。 + public string Text(int conditionColumn, string conditionValue, int resultColumn) { - var count = 0; - if (_disposed) return count; - if (tables == null) return count; - foreach (var table in tables) - { - if (table == null) continue; - _tables.Add(table); - count = count + 1; - } - return count; + var value = Value(conditionColumn, conditionValue, resultColumn); + return Text(value); } - /// 清除所有表,并释放系统资源。 - public virtual void Clear() + /// 释放系统资源。 + public virtual void Dispose() { + if (_disposed) return; if (_tables != null) { - foreach (var table in _tables) - { - if (table != null) - { - try { table.Dispose(); } catch { } - } - } - _tables.Clear(); + foreach (var table in _tables) RuntimeUtility.Dispose(table); + _tables = null; } - if (_exception != null) _exception = null; - _success = false; - } - - /// 释放系统资源。 - public virtual void Dispose() - { - Clear(); + RuntimeUtility.Dispose(_table); + _table = null; + _tables = null; _disposed = true; // GC.SuppressFinalize(this); } @@ -402,40 +225,79 @@ namespace Apewer.Source } /// 获取指定列的所有值,无效值不加入结果。 - public List ReadColumn(int column = 0, Func formatter = null) => OrmHelper.Column(this, (r) => (formatter ?? GetValueFormatter()).Invoke(Value(r, column))); + public T[] ReadColumn(int column = 0, Func formatter = null) => OrmHelper.Column(this, (r) => (formatter ?? GetValueFormatter()).Invoke(Value(r, column))); /// 获取指定列的所有值,无效值不加入结果。 /// - public List ReadColumn(string column, Func formatter = null) => OrmHelper.Column(this, (r) => (formatter ?? GetValueFormatter()).Invoke(Value(r, column))); + public T[] ReadColumn(string column, Func formatter = null) => OrmHelper.Column(this, (r) => (formatter ?? GetValueFormatter()).Invoke(Value(r, column))); /// 获取指定列的所有值,无效值不加入结果。 - public List ReadColumn(int column = 0) => OrmHelper.Column(this, (r) => Text(r, column)); + public string[] ReadColumn(int column = 0) => OrmHelper.Column(this, (r) => this.Text(r, column)); /// 获取指定列的所有值,无效值不加入结果。 /// - public List ReadColumn(string column) => OrmHelper.Column(this, (r) => Text(r, column)); + public string[] ReadColumn(string column) => OrmHelper.Column(this, (r) => this.Text(r, column)); #endregion - #region Static + #region IToJson - private static string Text(object value) + /// 转换为 Json 对象。 + public Json ToJson() { - var result = Constant.EmptyString; - if (value != null) + var columns = Json.NewArray(); + var rows = Json.NewArray(); + + var table = _table; + if (!_disposed && table != null) { - if (!value.Equals(DBNull.Value)) + var columnsCount = _table.Columns.Count; + for (var c = 0; c < columnsCount; c++) { - try + var dc = table.Columns[c]; + var column = Json.NewObject(); + column.SetProperty("name", dc.ColumnName); + column.SetProperty("type", dc.DataType.FullName); + columns.AddItem(column); + } + + var rowsCount = table.Rows.Count; + for (var r = 0; r < _table.Rows.Count; r++) + { + var row = Json.NewArray(); + for (var c = 0; c < columnsCount; c++) { - result = value.ToString(); + var v = Value(r, c); + if (v == null) row.AddItem(); + else if (v.Equals(DBNull.Value)) row.AddItem(); + else if (v is byte vByte) row.AddItem(vByte); + else if (v is short vInt16) row.AddItem(vInt16); + else if (v is int vInt32) row.AddItem(vInt32); + else if (v is long vInt64) row.AddItem(vInt64); + else if (v is float vSingle) row.AddItem(vSingle); + else if (v is double vDouble) row.AddItem(vDouble); + else if (v is decimal vDecimal) row.AddItem(vDecimal); + else if (v is bool vBoolean) row.AddItem(vBoolean); + else if (v is byte[] vBytes) row.AddItem(vBytes.Base64()); + else if (v is DateTime vDateTime) row.AddItem(vDateTime.Lucid()); + else row.AddItem(v.ToString()); } - finally { } + rows.AddItem(row); } } - return result; + + var jsonObject = Json.NewObject(); + jsonObject.SetProperty("success", _success); + jsonObject.SetProperty("message", _message); + jsonObject.SetProperty("columns", columns); + jsonObject.SetProperty("rows", rows); + return jsonObject; } + #endregion + + #region Static + private static T ForceFormatter(object input) => (T)input; private static T TextFormatter(object input) => (T)(Text(input) as object); @@ -443,56 +305,68 @@ namespace Apewer.Source private static ObjectDisposedException DisposedException { get { return new ObjectDisposedException(typeof(Query).FullName); } } /// 简单查询:取结果中第 0 列所有单元格的文本形式,可指定查询后关闭服务器连接,返回结果中不包含无效文本。 - public static List SimpleColumn(IDatabaseQuery database, string statement, bool dispose = false) + public static string[] SimpleColumn(IDbClientAdo database, string statement, bool dispose = false) { - var list = new List(); - if (database == null) return list; + if (database == null) return new string[0]; + var ab = new ArrayBuilder(); using (var query = database.Query(statement)) { var rows = query.Rows; if (rows > 0) { - list.Capacity = query.Rows; var added = 0; for (int i = 0; i < rows; i++) { var cell = Trim(query.Text(i)); if (string.IsNullOrEmpty(cell)) continue; - list.Add(cell); + ab.Add(cell); added++; } - list.Capacity = added; } } if (dispose) RuntimeUtility.Dispose(database); - return list; + return ab.Export(); } /// 简单查询:取结果中第 0 行、第 0 列单元格中的文本,可指定查询后关闭服务器连接。 - public static string SimpleCell(IDatabaseQuery database, string statement, bool dispose = false) + public static string SimpleCell(IDbClientAdo database, string statement, bool dispose = false) { if (database == null) return ""; - var vquery = database.Query(statement); - var vcell = Trim(vquery.Text()); - vquery.Dispose(); + var query = database.Query(statement); + var cell = Trim(Query.Text(query.Value())); + query.Dispose(); if (dispose) RuntimeUtility.Dispose(database); - return vcell; + return cell; } #endregion #region Extension - /// - internal static DateTime DateTime(IQuery query, int row, string column) + internal static string Text(object value) + { + var result = Constant.EmptyString; + if (value != null) + { + if (!value.Equals(DBNull.Value)) + { + try + { + result = value.ToString(); + } + finally { } + } + } + return result; + } + + internal static Class DateTime(object value) { - if (query == null) return ClockUtility.Origin; - var value = query.Value(row, column); - if (value == null) return ClockUtility.Origin; - if (value is DateTime) return (DateTime)value; + if (value == null) return null; + if (value is DateTime) return null; DateTime result; var parsed = System.DateTime.TryParse(value.ToString(), out result); - return parsed ? result : ClockUtility.Origin; + return parsed ? new Class(result) : null; } #endregion diff --git a/Apewer/Source/Record.cs b/Apewer/Source/Record.cs index 91b32a4..446834a 100644 --- a/Apewer/Source/Record.cs +++ b/Apewer/Source/Record.cs @@ -9,34 +9,34 @@ namespace Apewer.Source { /// 数据库记录通用字段模型。 + /// 带有 Independent 特性的模型不包含此类型声明的属性。 [Serializable] - public class Record : IRecord + public abstract class Record : IRecord { - const int KeyLength = 64; + const int KeyLength = 191; private string _key = null; private long _flag = 0; - /// 记录唯一键,一般使用 GUID 的字符串形式。 + /// 记录主键,一般使用 GUID 的字符串形式。 + /// 带有 Independent 特性的模型不包含此属性。 [Column("_key", ColumnType.NVarChar, KeyLength)] - public virtual string Key { get { return _key; } set { _key = Compact(value, KeyLength); } } + public string Key { get { return _key; } set { _key = Compact(value, KeyLength); } } /// 记录的标记,Int64 类型,区分记录的状态。 + /// 带有 Independent 特性的模型不包含此属性。 [Column("_flag", ColumnType.Integer)] - public virtual long Flag { get { return _flag; } set { _flag = value; } } + public long Flag { get { return _flag; } set { _flag = value; } } /// 重置 Key 属性的值。 - public virtual void ResetKey() => _key = GenerateKey(); + public virtual void ResetKey() => _key = TextUtility.Key(); /// public Record() => ResetKey(); #region static - /// 生成新主键。 - public static string GenerateKey() => Guid.NewGuid().ToString().ToLower().Replace("-", ""); - internal static void FixProperties(IRecord record) { if (record == null) return; @@ -120,7 +120,7 @@ namespace Apewer.Source #region 运算符。 /// 从 Record 到 Boolean 的隐式转换,判断 Record 对象不为 NULL。 - public static implicit operator bool(Record instance) => instance!=null; + public static implicit operator bool(Record instance) => instance != null; #endregion } diff --git a/Apewer/Source/TableAttribute.cs b/Apewer/Source/TableAttribute.cs index d6062d5..b9bc15d 100644 --- a/Apewer/Source/TableAttribute.cs +++ b/Apewer/Source/TableAttribute.cs @@ -7,45 +7,80 @@ namespace Apewer.Source { /// 数据库中的表。 + /// + /// Name: 数据库的表名。 + /// Store: 数据存储区名称。 + /// [Serializable] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] public sealed class TableAttribute : Attribute { private string _name; - private bool _allprops = false; - private bool _independent = false; + private string _store; - /// - public TableAttribute(string name = null, bool allProperties = false) + /// 标记表属性。 + public TableAttribute(string name = null, string store = null) { _name = TableStructure.RestrictName(name, string.IsNullOrEmpty(name)); - _allprops = allProperties; + _store = string.IsNullOrEmpty(store) ? null : TableStructure.RestrictName(store, false); } /// 表名。 - public string Name - { - get => _name; - set => _name = TableStructure.RestrictName(value, false); - } + public string Name { get => _name; } - /// - public bool Independent - { - get => _independent; - internal set => _independent = value; - } + /// 存储名。 + public string Store { get => _store; } + + /// 表的说明信息。(需要数据库客户端支持) + public string Description { get; set; } + + /// 独立结构,不依赖 Record 公共属性。 + internal bool Independent { get; set; } + + /// 使用模型的所有属性,对缺少 Column 特性的属性使用默认参数的 Column 特性。 + public bool AllProperties { get; set; } - /// 使用所有属性,即使属性不带有 Column 特性。 - public bool AllProperties + private static Dictionary _tac = new Dictionary(); + + /// 解析表特性,默认使用缓存以提升性能。 + public static TableAttribute Parse(bool useCache = true) where T : IRecord => Parse(typeof(T), useCache); + + /// 解析表特性,默认使用缓存以提升性能。 + public static TableAttribute Parse(Type type, bool useCache = true) { - get => _allprops; - set => _allprops = value; - } + var cacheKey = type.FullName; + if (useCache) + { + var hint = null as TableAttribute; + lock (_tac) + { + if (_tac.ContainsKey(cacheKey)) + { + hint = _tac[cacheKey]; + } + } + if (hint != null) return hint; + } + + // throw new Exception($"类型 {type.FullName} 不包含 {typeof(TableAttribute).FullName}。"); + var tas = type.GetCustomAttributes(typeof(TableAttribute), false); + if (tas.LongLength < 1L) return null; - /// - public override int GetHashCode() => _name.GetHashCode(); + var ta = (TableAttribute)tas[0]; + if (string.IsNullOrEmpty(ta.Name)) ta._name = "_" + type.Name; + ta.Independent = RuntimeUtility.Contains(type, true); + + if (useCache) + { + lock (_tac) + { + if (!_tac.ContainsKey(cacheKey)) _tac.Add(cacheKey, ta); + } + } + + return ta; + } } diff --git a/Apewer/Source/TableStructure.cs b/Apewer/Source/TableStructure.cs index c0e423c..1b248fe 100644 --- a/Apewer/Source/TableStructure.cs +++ b/Apewer/Source/TableStructure.cs @@ -9,290 +9,136 @@ using System.Text; namespace Apewer.Source { - /// + /// 表结构。 [Serializable] public sealed class TableStructure { - private string _tablename = Constant.EmptyString; - private bool _independent = false; + #region Instance - private Dictionary _columns = new Dictionary(); + TableAttribute _attribute = null; + bool _independent = false; + string _name = null; + string _description = null; + bool _allprops = false; + ColumnAttribute _key = null; + ColumnAttribute _flag = null; + ColumnAttribute[] _columns = null; + Type _model = null; - internal TableStructure() { } + private TableStructure() { } /// 不依赖 Record 公共属性。 - public bool Independent - { - get => _independent; - private set => _independent = value; - } + public bool Independent { get => _independent; } /// 表名称。 - public string Table - { - get => _tablename; - private set => _tablename = value ?? ""; - } + public string Name { get => _name; } - /// 列信息。 - public Dictionary Columns - { - get => _columns; - private set => _columns = value; - } + /// 表的说明信息。 + public string Description { get => _description; } - #region cache + /// 使用模型的所有属性,自动对属性添加缺少的 Column 特性。 + public bool AllProperties { get => _allprops; } - private static Dictionary _tsc = new Dictionary(); + /// 此结构的特性。 + public TableAttribute Attribute { get => _attribute; } + + /// 使用此结构的记录模型。 + public Type Model { get => _model; } - private static Dictionary _tac = new Dictionary(); + /// 主键。 + public ColumnAttribute Key { get => _key; } + + /// 列信息。 + public ColumnAttribute[] Columns { get => _columns; } #endregion - #region static + #region TableStructure - /// - /// - /// - public static TableStructure ParseModel(object entity, bool useCache = true) - { - if (entity == null) throw new ArgumentNullException("参数无效"); - return ParseModel(entity.GetType(), useCache); - } + private static Dictionary _tsc = new Dictionary(); - /// - /// - /// - public static TableStructure ParseModel(bool useCache = true) where T : IRecord => ParseModel(typeof(T), useCache); + /// 解析表结构。 + public static TableStructure Parse(bool useCache = true) where T : IRecord => Parse(typeof(T), useCache); - /// - /// - /// - public static TableStructure ParseModel(Type model, bool useCache = true) + /// 解析表结构。 + public static TableStructure Parse(Type model, bool useCache = true) { var type = model; - if (type == null) throw new ArgumentNullException("参数无效"); + if (type == null || !type.IsClass || type.IsAbstract) return null; // 使用缓存。 var cacheKey = type.FullName; if (useCache) { - var hint = null as TableStructure; lock (_tsc) { - if (_tsc.ContainsKey(cacheKey)) - { - hint = _tsc[cacheKey]; - } + TableStructure cached; + if (_tsc.TryGetValue(cacheKey, out cached)) return cached; } - if (hint != null) return hint; } - // 检查基类。 - // if (type.BaseType.FullName.Equals(typeof(DatabaseRecord).FullName) == false) return "基类不受支持。"; + // 获取 Table Attribute。 + var ta = TableAttribute.Parse(type); - // 检查 Attribute。 - var ta = ParseTable(type); - - // 获取所有属性。 + // 遍历所有属性。 var properties = type.GetProperties(); - if (properties.LongLength < 1L) throw new Exception(TextUtility.Merge("类 ", type.FullName, " 不包含属性。")); - - // Record 根类属性名。 - var roots = GetRootProperties(); - - // 检查字段定义。键:属性名称。 - var columns = new Dictionary(); - foreach (var property in properties) - { - var ca = ParseColumn(type, property, ta); - if (ca == null) continue; - - // 检查冗余。 - foreach (var column in columns) + var key = null as ColumnAttribute; + var flag = null as ColumnAttribute; + var columns = new ColumnAttribute[properties.Length]; + var columnsCount = 0; + if (properties.Length > 0) + { + var addedFields = new List(properties.Length); + foreach (var property in properties) { - if (column.Value.Field == ca.Field) - { - throw new Exception(TextUtility.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 的列名称存在冗余。")); - } + // 解析 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; } - - // 检查基类。 - if (roots.Contains(ca.Property.Name)) ca.Independent = true; - - columns.Add(property.Name, ca); } - // if (columns.Count < 1) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 不包含可用的列。")); + if (columnsCount > 0 && columnsCount != columns.Length) columns = columns.Slice(0, columnsCount); - // 排序。 - columns = SortColumns(columns); + // 排序,将 Key 和 Flag 排在最前。 + columns = ColumnAttribute.Sort(columns); // 返回结果。 var ts = new TableStructure(); - ts.Table = ta.Name; - ts.Independent = ta.Independent; - ts.Columns = columns; + ts._attribute = ta; + ts._key = key; + ts._flag = flag; + ts._name = ta.Name; + ts._description = ta.Description; + ts._allprops = ta.AllProperties; + ts._independent = ta.Independent; + ts._columns = columns; + ts._model = model; // 加入缓存。 if (useCache) { lock (_tsc) { - if (!_tsc.ContainsKey(cacheKey)) - { - _tsc.Add(cacheKey, ts); - } + if (!_tsc.ContainsKey(cacheKey)) _tsc.Add(cacheKey, ts); } } return ts; } - /// - /// " - public static TableAttribute ParseTable(bool useCache = true) where T : IRecord => ParseTable(typeof(T), useCache); - - /// - /// " - public static TableAttribute ParseTable(Type type, bool useCache = true) - { - // 使用缓存。 - var cacheKey = type.FullName; - if (useCache) - { - var hint = null as TableAttribute; - lock (_tac) - { - if (_tac.ContainsKey(cacheKey)) - { - hint = _tac[cacheKey]; - } - } - if (hint != null) return hint; - } - - var tas = type.GetCustomAttributes(typeof(TableAttribute), false); - if (tas.LongLength < 1L) throw new Exception(TextUtility.Merge("类 ", type.FullName, " 不包含 ", typeof(TableAttribute).FullName, "。")); - if (tas.LongLength > 1L) throw new Exception(TextUtility.Merge("类 ", type.FullName, " 包含多个 ", typeof(TableAttribute).FullName, "。")); - - var ta = (TableAttribute)tas[0]; - if (TextUtility.IsBlank(ta.Name)) - { - ta = new TableAttribute("_" + type.Name); - if (TextUtility.IsBlank(ta.Name)) throw new Exception(TextUtility.Merge("类 ", type.FullName, " 的表名称无效。")); - } - - ta.Independent = RuntimeUtility.Contains(type, true); - - // 加入缓存。 - if (useCache) - { - lock (_tac) - { - if (!_tac.ContainsKey(cacheKey)) - { - _tac.Add(cacheKey, ta); - } - } - } - - return ta; - } - - /// - /// Exception" - static ColumnAttribute ParseColumn(Type type, PropertyInfo property, TableAttribute ta) - { - // 检查 ColumnAttribute。 - ColumnAttribute ca; - { - var cas = property.GetCustomAttributes(typeof(ColumnAttribute), false); - if (cas.LongLength < 1L) - { - if (!ta.AllProperties) return null; - ca = new ColumnAttribute(); - } - else ca = (ColumnAttribute)cas[0]; - } - - // 检查属性方法。 - var getter = property.GetGetMethod(false); - var setter = property.GetSetMethod(false); - if (getter == null || getter.IsStatic) return null; - if (setter == null || setter.IsStatic) return null; - // getter 或 setter 存在异常时忽略此属性,而不是抛出异常。 - // if (getter == null) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 不支持获取。")); - // if (setter == null) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 不支持设置。")); - - // 检查列名称。 - if (TextUtility.IsBlank(ca.Field)) - { - ca = new ColumnAttribute("_" + property.Name, ca.Type, ca.Length, true); - if (TextUtility.IsBlank(ca.Field)) throw new Exception(TextUtility.Merge("类 ", type.FullName, "中,属性 ", property.Name, " 的列名称无效。")); - } - - // 类型兼容。 - var pt = property.PropertyType; - if (pt.Equals(typeof(byte[]).FullName)) ca.Type = ColumnType.Bytes; - else if (pt.Equals(typeof(Byte))) ca.Type = ColumnType.Integer; - else if (pt.Equals(typeof(SByte))) ca.Type = ColumnType.Integer; - else if (pt.Equals(typeof(Int16))) ca.Type = ColumnType.Integer; - else if (pt.Equals(typeof(UInt16))) ca.Type = ColumnType.Integer; - else if (pt.Equals(typeof(Int32))) ca.Type = ColumnType.Integer; - else if (pt.Equals(typeof(UInt32))) ca.Type = ColumnType.Integer; - else if (pt.Equals(typeof(Int64))) ca.Type = ColumnType.Integer; - else if (pt.Equals(typeof(Single))) ca.Type = ColumnType.Float; - else if (pt.Equals(typeof(Double))) ca.Type = ColumnType.Float; - else if (pt.Equals(typeof(Decimal))) ca.Type = ColumnType.Float; - else if (pt.Equals(typeof(DateTime))) ca.Type = ColumnType.DateTime; - else if (pt.Equals(typeof(String))) - { - switch (ca.Type) - { - case ColumnType.Bytes: - case ColumnType.Integer: - case ColumnType.Float: - case ColumnType.DateTime: - //throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 的类型不受支持。")); - ca.Type = ColumnType.NText; - break; - } - } - else - { - ca.Type = ColumnType.NText; - } - - ca.Property = property; - - return ca; - } - - /// 排序。 - static Dictionary SortColumns(Dictionary columns) - { - // if (type.BaseType.FullName.Equals(typeof(Record).FullName)) // 仅当使用基类时排序。 - - var sorted = new Dictionary(); - - if (columns.ContainsKey("Key")) sorted.Add("Key", columns["Key"]); - if (columns.ContainsKey("Flag")) sorted.Add("Flag", columns["Flag"]); - if (columns.ContainsKey("Created")) sorted.Add("Created", columns["Created"]); - if (columns.ContainsKey("Updated")) sorted.Add("Updated", columns["Updated"]); - - foreach (var property in columns.Keys) - { - if (property == "Key") continue; - if (property == "Flag") continue; - if (property == "Created") continue; - if (property == "Updated") continue; - - sorted.Add(property, columns[property]); - } + #endregion - return sorted; - } + #region TableAttribute /// 限定表名称/列名称。 /// 名称。 @@ -315,110 +161,84 @@ namespace Apewer.Source return lower; } - static IDataParameter GenerateDataParameter(IRecord entity, ColumnAttribute attribute, CreateDataParameterCallback callback) + static IDataParameter CreateParameter(IRecord record, ColumnAttribute ca, Func callback) { - var property = attribute.Property; + var property = ca.Property; if (property == null) return null; var getter = property.GetGetMethod(); if (getter == null) return null; - var parameter = null as IDataParameter; - var value = getter.Invoke(entity, null); + var value = getter.Invoke(record, null); - // - if (attribute.Type == ColumnType.Bytes || attribute.Type == ColumnType.Integer || attribute.Type == ColumnType.Float) + if (ca.Type == ColumnType.Bytes || ca.Type == ColumnType.Integer || ca.Type == ColumnType.Float) { - var temp = value; - if (property.PropertyType.FullName == typeof(Decimal).FullName) - { - temp = NumberUtility.Double(temp.ToString()); - } - parameter = callback(new Parameter(attribute.Field, temp, attribute.Type, attribute.Length)); + return callback(new Parameter(ca.Field, value, ca.Type, ca.Length)); } - else if (attribute.Type == ColumnType.DateTime) + + if (ca.Type == ColumnType.DateTime) { - parameter = callback(new Parameter(attribute.Field, value, attribute.Type, 0)); + return callback(new Parameter(ca.Field, value, ca.Type, 0)); } - else if (property.PropertyType.Equals(typeof(String))) + + if (property.PropertyType.Equals(typeof(String))) { var text = value as string; if (text == null) text = ""; - if (attribute.Length > 0) + if (ca.Length > 0) { - switch (attribute.Type) + switch (ca.Type) { case ColumnType.VarChar: case ColumnType.NVarChar: - text = TextUtility.Left(text, attribute.Length); + text = TextUtility.Left(text, ca.Length); break; - case ColumnType.VarChar255: - case ColumnType.NVarChar255: - text = TextUtility.Left(text, 255); + case ColumnType.VarChar191: + case ColumnType.NVarChar191: + text = TextUtility.Left(text, 191); break; } } - parameter = callback(new Parameter(attribute.Field, text, attribute.Type, attribute.Length)); - } - else - { - var text = (value == null) ? TextUtility.Empty : value.ToString(); - parameter = callback(new Parameter(attribute.Field, text, attribute.Type, attribute.Length)); + return callback(new Parameter(ca.Field, text, ca.Type, ca.Length)); } - return parameter; + + var defaultText = (value == null) ? TextUtility.Empty : value.ToString(); + return callback(new Parameter(ca.Field, defaultText, ca.Type, ca.Length)); } - /// 生成 IDataParameter 列表,用于 Insert 或 Update。 - /// - public List CreateDataParameters(IRecord entity, CreateDataParameterCallback callback, params string[] excluded) + /// 生成 IDataParameter 列表,用于 Insert 和 Update 方法。 + public IDataParameter[] CreateParameters(IRecord record, Func callback, params string[] excludeds) { - if (entity == null) throw new ArgumentNullException(nameof(entity)); - if (callback == null) throw new ArgumentNullException(nameof(excluded)); - - entity.FixProperties(); + if (record == null || callback == null) return null; + record.FixProperties(); - var list = new List(); - foreach (var column in Columns) + var list = new List(_columns.Length); + foreach (var ca in Columns) { - var attribute = column.Value; - if (ParseTable(entity.GetType()).Independent && attribute.Independent) continue; + if (ca == null) continue; - var parameter = GenerateDataParameter(entity, attribute, callback); + var parameter = CreateParameter(record, ca, callback); if (parameter == null) continue; var add = true; - foreach (var exclude in excluded) + if (excludeds != null) { var lower = parameter.ParameterName.ToLower(); - if (lower == exclude.ToLower()) + foreach (var excluded in excludeds) { - add = false; + if (string.IsNullOrEmpty(excluded)) continue; + if (lower == excluded.ToLower()) + { + add = false; + break; + } } } + if (add) list.Add(parameter); } - return list; - } - - #endregion - - #region - - /// 获取 Record 根类中的属性名称。 - static List GetRootProperties() - { - var list = new List(); - var type = typeof(Record); - var properties = type.GetProperties(); - foreach (var property in properties) - { - if (RuntimeUtility.Contains(property, false)) - { - list.Add(property.Name); - } - } - return list; + return list.ToArray(); } #endregion diff --git a/Apewer/Source/Timeout.cs b/Apewer/Source/Timeout.cs index 037bbfd..badf2ea 100644 --- a/Apewer/Source/Timeout.cs +++ b/Apewer/Source/Timeout.cs @@ -5,7 +5,7 @@ namespace Apewer.Source /// 超时。 [Serializable] - public struct Timeout + public class Timeout { private int _connect, _query, _execute; @@ -40,7 +40,7 @@ namespace Apewer.Source } /// 默认超时设置:连接 10000、查询 60000,执行 60000。 - public static Timeout Default { get { return new Timeout(10000, 60000, 60000); } } + public static Timeout Default { get => new Timeout(10000, 60000, 60000); } } diff --git a/Apewer/StringPairs.cs b/Apewer/StringPairs.cs index 6d3783f..3423110 100644 --- a/Apewer/StringPairs.cs +++ b/Apewer/StringPairs.cs @@ -18,6 +18,12 @@ namespace Apewer /// public StringPairs(int capacity) : base(capacity) { } + /// + public string this[string key] + { + get { return GetValue(key); } + } + /// 添加项。返回错误信息。 public string Add(string key, string value) { diff --git a/Apewer/TextUtility.cs b/Apewer/TextUtility.cs index a809d23..1db8dcd 100644 --- a/Apewer/TextUtility.cs +++ b/Apewer/TextUtility.cs @@ -513,18 +513,21 @@ namespace Apewer public static double Similarity(string arg1, string arg2) => Levenshtein.Compute(arg1, arg2).Rate; /// 生成新的 GUID,默认为小写,且不包含连字符,长度为 32 位。 - public static string NewGuid(bool hyphenation = false, bool lower = true) + public static string Guid(bool hyphenation = false, bool lower = true) { - var guid = Guid.NewGuid(); + var guid = System.Guid.NewGuid(); if (!hyphenation && lower) return guid.ToString("n"); - var text = Guid.NewGuid().ToString(); + var text = System.Guid.NewGuid().ToString(); if (lower) text = text.ToLower(); else text = text.ToUpper(); if (!hyphenation) text = text.Replace("-", ""); return text; } + /// 生成新主键。 + public static string Key() => System.Guid.NewGuid().ToString().ToLower().Replace("-", ""); + /// 生成随机字符串,出现的字符由字符池指定,默认池包含数字和字母。 /// 随机字符串的长度。 /// 字符池,字符池中每个字符在随机字符串中出现的概率约等。 diff --git a/Apewer/Web/ApiOptions.cs b/Apewer/Web/ApiOptions.cs index 4942a13..4244f8d 100644 --- a/Apewer/Web/ApiOptions.cs +++ b/Apewer/Web/ApiOptions.cs @@ -34,6 +34,10 @@ namespace Apewer.Web /// 默认值:不缩进。 public bool JsonIndent { get; set; } = false; + /// 在响应头中设置 Content-Security-Policy,要求浏览器升级资源链接,使用 HTTPS。 + /// 默认值:不要求。在 HTTPS 页面中,不自动升级 HTTP 资源。 + public bool UpgradeHttps { get; set; } = false; + /// 允许响应中包含 Exception 对象的属性。 /// 默认值:不允许。 public bool WithException { get; set; } = false; diff --git a/Apewer/Web/ApiUtility.cs b/Apewer/Web/ApiUtility.cs index 0daeca6..e98172d 100644 --- a/Apewer/Web/ApiUtility.cs +++ b/Apewer/Web/ApiUtility.cs @@ -208,7 +208,7 @@ namespace Apewer.Web if (url.IsEmpty()) return "ApiController 无效。"; var s = Json.NewObject(); - s.SetProperty("random", TextUtility.NewGuid()); + s.SetProperty("random", TextUtility.Guid()); s.SetProperty("application", application.IsEmpty() ? controller.Request.Application : application); s.SetProperty("function", function.IsEmpty() ? controller.Request.Function : function); s.SetProperty("data", controller.Request.Data); @@ -379,13 +379,11 @@ namespace Apewer.Web public static string Respond(ApiResponse response, Json data, bool lower = true) { if (response == null) return "Response 对象无效。"; - if (data != null) { if (lower) data = Json.Lower(data); - response.Data.Reset(data); + response.Data = data; } - return null; } diff --git a/Apewer/Web/DefaultController.cs b/Apewer/Web/DefaultController.cs new file mode 100644 index 0000000..c010f59 --- /dev/null +++ b/Apewer/Web/DefaultController.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Web +{ + + /// 默认控制器。 + public abstract class DefaultController : ApiController + { + + /// 默认控制器实例。 + public DefaultController() : base((c) => ((DefaultController)c).Process()) { } + + /// 处理请求。 + public abstract void Process(); + + } + +} diff --git a/Apewer/_Common.props b/Apewer/_Common.props index fd8da86..71e086c 100644 --- a/Apewer/_Common.props +++ b/Apewer/_Common.props @@ -5,15 +5,16 @@ true + bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml latest - + Apewer Libraries - 6.4.0 + 6.4.1 diff --git a/Apewer/_Extensions.cs b/Apewer/_Extensions.cs index 456c229..a4a8c4b 100644 --- a/Apewer/_Extensions.cs +++ b/Apewer/_Extensions.cs @@ -45,6 +45,9 @@ public static class Extensions /// 判断静态属性。 public static bool IsStatic(this PropertyInfo @this) => RuntimeUtility.IsStatic(@this); + /// 以安全的方式获取消息内容,对无效的 Exception 返回 NULL 值。 + public static string Message(this Exception ex) => RuntimeUtility.Message(ex); + #endregion #region Number @@ -152,6 +155,12 @@ public static class Extensions /// 剪取后的内容,不包含 head 和 foot。 public static string Cut(this string text, string head = null, string foot = null) => TextUtility.Cut(text, head, foot); + /// 约束字符串中的字符,只包含指定的字符。 + public static string Restrict(this string text, char[] chars) => TextUtility.Restrict(text, chars); + + /// 约束字符串中的字符,只包含指定的字符。 + public static string Restrict(this string text, string chars) => TextUtility.Restrict(text, chars); + #endregion #region Byte[] @@ -200,6 +209,7 @@ public static class Extensions public static long Stamp(this DateTime @this, bool byMilliseconds = true) => ClockUtility.Stamp(@this, byMilliseconds); /// 转换为易于阅读的文本。 + /// 格式:1970- public static string Lucid(this DateTime @this, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) => ClockUtility.Lucid(@this, date, time, seconds, milliseconds); /// 转换为紧凑的文本。 @@ -208,6 +218,10 @@ public static class Extensions /// 当前 DateTime 为闰年。 public static bool LeapYear(this DateTime @this) => ClockUtility.IsLeapYear(@this); + /// 从毫秒时间戳获取 DateTime 对象。发生异常且不允许异常时将返回 1970-01-01 00:00:00.000。 + /// + public static DateTime DateTime(this long stamp, bool throwException = true) => ClockUtility.FromStamp(stamp, throwException); + #endregion #region Json @@ -252,7 +266,7 @@ public static class Extensions /// 忽略属性名称大小写。 /// 忽略的属性名称字符。 /// 强制填充,忽略 的 Serializable 特性。 - public static List Array(this Json @this, bool ignoreCase = true, string ignoreChars = null, bool force = false) where T : class, new() => Apewer.Json.Array(@this, ignoreCase, ignoreChars, force); + public static T[] Array(this Json @this, bool ignoreCase = true, string ignoreChars = null, bool force = false) where T : class, new() => Apewer.Json.Array(@this, ignoreCase, ignoreChars, force); /// 设置属性名称为小写。 public static Json Lower(this Json @this) => Apewer.Json.Lower(@this); @@ -411,21 +425,42 @@ public static class Extensions /// 修补基本属性。 public static void FixProperties(this IRecord @this) => Record.FixProperties(@this); - /// - public static DateTime DateTime(this IQuery @this, int row, string column) => Query.DateTime(@this, row, column); + /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 + public static Class DateTime(this IQuery @this, int row = 0, int column = 0) => @this == null ? null : Query.DateTime(@this.Value(row, column)); - /// + /// 获取默认表中指定单元格的内容。从第 0 行开始。 + public static Class DateTime(this IQuery @this, int row, string column) => @this == null ? null : Query.DateTime(@this.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 + public static Int32 Int32(this IQuery @this, int row = 0, int column = 0) => @this == null ? 0 : Int32(@this.Text(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行开始。 public static Int32 Int32(this IQuery @this, int row, string column) => @this == null ? 0 : Int32(@this.Text(row, column)); - /// + /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 + public static Int64 Int64(this IQuery @this, int row = 0, int column = 0) => @this == null ? 0L : Int64(@this.Text(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行开始。 public static Int64 Int64(this IQuery @this, int row, string column) => @this == null ? 0L : Int64(@this.Text(row, column)); - /// + /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 + public static Decimal Decimal(this IQuery @this, int row = 0, int column = 0) => @this == null ? 0M : Decimal(@this.Text(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行开始。 public static Decimal Decimal(this IQuery @this, int row, string column) => @this == null ? 0M : Decimal(@this.Text(row, column)); - /// + /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。> + public static Double Double(this IQuery @this, int row = 0, int column = 0) => @this == null ? 0D : Double(@this.Text(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行开始。> public static Double Double(this IQuery @this, int row, string column) => @this == null ? 0D : Double(@this.Text(row, column)); + /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 + public static string Text(this IQuery @this, int row = 0, int column = 0) => @this == null ? null : Query.Text(@this.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行开始。 + public static string Text(this IQuery @this, int row, string column) => @this == null ? null : Query.Text(@this.Value(row, column)); + #endregion #region Web @@ -456,7 +491,7 @@ public static class Extensions 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, Record record, bool lower = true) => ApiUtility.Respond(@this, record, lower); + 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); diff --git a/ChangeLog.md b/ChangeLog.md index 2d38ece..6dca4bf 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,16 @@  ### 最新提交 +### 6.4.1 +- Clock:增加 long.DateTime() 方法; +- Json:引用的 List 现改为数组; +- Source:SqlServer 重命名为 SqlClient,并支持 .NET Standard; +- Source:Query 和 Execute 支持 ToJson 方法; +- Source:TableAttribute 增加 Store 属性,用于 Accessor 匹配; +- Source:ORM 引用的 List 现改为数组; +- Source:增加 Begin、Commit 和 Rollback,用于控制事务; +- Web:不再要求控制器拥有 public 修饰符。 + ### 6.4.0 - 重构项目,减少了主类库的依赖项和文件体积; - BytesUtility:新类,由 BinaryUtility 重命名而来;