diff --git a/Apewer.Source/Apewer.Source.csproj b/Apewer.Source/Apewer.Source.csproj
index 1c15c33..922a466 100644
--- a/Apewer.Source/Apewer.Source.csproj
+++ b/Apewer.Source/Apewer.Source.csproj
@@ -32,17 +32,6 @@
-
-
- $(DefineConstants);MYSQL_6_10;
-
-
-
-
-
-
-
-
$(DefineConstants);MYSQL_6_9;
diff --git a/Apewer.Source/Source/Access.cs b/Apewer.Source/Source/Access.cs
index 0863201..f8b34e5 100644
--- a/Apewer.Source/Source/Access.cs
+++ b/Apewer.Source/Source/Access.cs
@@ -67,8 +67,8 @@ namespace Apewer.Source
}
/// 连接数据库,若未连接则尝试连接。
- /// 是否已连接。
- public bool Connect()
+ /// 错误信息。
+ public string Connect()
{
if (_connection == null)
{
@@ -77,19 +77,20 @@ namespace Apewer.Source
}
else
{
- if (_connection.State == ConnectionState.Open) return true;
+ if (_connection.State == ConnectionState.Open) return null;
}
try
{
_connection.Open();
- if (_connection.State == ConnectionState.Open) return true;
+ if (_connection.State == ConnectionState.Open) return null;
}
catch (Exception ex)
{
Logger.Error(nameof(Access), "Connect", ex, _connstr);
Close();
+ return ex.Message;
}
- return false;
+ return "连接失败。";
}
/// 释放对象所占用的系统资源。
@@ -124,7 +125,8 @@ namespace Apewer.Source
/// 启动事务。
public string Begin(bool commit, Class isolation)
{
- if (!Connect()) return "未连接。";
+ var connect = Connect();
+ if (connect.NotEmpty()) return connect;
if (_transaction != null) return "存在已启动的事务,无法再次启动。";
try
{
@@ -192,7 +194,7 @@ namespace Apewer.Source
if (sql.IsBlank()) return Example.InvalidQueryStatement;
var connected = Connect();
- if (!connected) return Example.InvalidQueryConnection;
+ if (connected.NotEmpty()) return new Query(false, connected);
try
{
@@ -236,7 +238,7 @@ namespace Apewer.Source
if (sql.IsBlank()) return Example.InvalidExecuteStatement;
var connected = Connect();
- if (!connected) return Example.InvalidExecuteConnection;
+ if (connected.NotEmpty()) return new Execute(false, connected);
var inTransaction = _transaction != null;
if (!inTransaction) Begin();
diff --git a/Apewer.Source/Source/ColumnInfo.cs b/Apewer.Source/Source/ColumnInfo.cs
new file mode 100644
index 0000000..eca78cb
--- /dev/null
+++ b/Apewer.Source/Source/ColumnInfo.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Apewer.Source
+{
+
+ /// 列信息。
+ [Serializable]
+ public sealed class ColumnInfo
+ {
+
+ /// 字段。
+ public string Name { get; set; }
+
+ /// 类型。
+ public string Type { get; set; }
+
+ /// 长度。
+ public int Length { get; set; }
+
+ /// 是主键。
+ public int IsKey { get; set; }
+
+ }
+
+}
diff --git a/Apewer.Source/Source/MySql.cs b/Apewer.Source/Source/MySql.cs
index 85965b3..11d1c90 100644
--- a/Apewer.Source/Source/MySql.cs
+++ b/Apewer.Source/Source/MySql.cs
@@ -1,6 +1,6 @@
#if MYSQL_6_9 || MYSQL_6_10
-/* 2021.10.14 */
+/* 2021.11.07 */
using Externals.MySql.Data.MySqlClient;
using System;
@@ -45,10 +45,10 @@ namespace Apewer.Source
{
_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 a = address ?? "";
+ var s = store ?? "";
+ var u = user ?? "";
+ var p = pass ?? "";
var cs = $"server={a}; database={s}; uid={u}; pwd={p}; ";
_connectionstring = cs;
_storename = new Class(s);
@@ -77,7 +77,7 @@ namespace Apewer.Source
public string ConnectionString { get => _connectionstring; }
///
- public bool Connect()
+ public string Connect()
{
if (_connection == null)
{
@@ -86,7 +86,7 @@ namespace Apewer.Source
}
else
{
- if (_connection.State == ConnectionState.Open) return true;
+ if (_connection.State == ConnectionState.Open) return null;
}
// try
@@ -94,8 +94,8 @@ namespace Apewer.Source
_connection.Open();
switch (_connection.State)
{
- case ConnectionState.Open: return true;
- default: return false;
+ case ConnectionState.Open: return null;
+ default: return $"连接失败,当前处于 {_connection.State} 状态。";
}
}
// catch (Exception ex)
@@ -138,7 +138,7 @@ namespace Apewer.Source
/// 启动事务。
public string Begin(bool commit, Class isolation)
{
- if (!Connect()) return "未连接。";
+ if (Connect() != null) return "未连接。";
if (_transaction != null) return "存在已启动的事务,无法再次启动。";
try
{
@@ -203,7 +203,7 @@ namespace Apewer.Source
if (sql.IsBlank()) return Example.InvalidQueryStatement;
var connected = Connect();
- if (!connected) return Example.InvalidQueryConnection;
+ if (connected.NotEmpty()) return Example.InvalidQueryConnection;
try
{
@@ -244,7 +244,7 @@ namespace Apewer.Source
if (sql.IsBlank()) return Example.InvalidExecuteStatement;
var connected = Connect();
- if (!connected) return Example.InvalidExecuteConnection;
+ if (connected.NotEmpty()) return new Execute(false, connected);
var inTransaction = _transaction != null;
if (!inTransaction) Begin();
@@ -290,7 +290,7 @@ namespace Apewer.Source
dps = new List(count);
foreach (var p in parameters)
{
- var dp = CreateDataParameter(p);
+ var dp = Parameter(p);
dps.Add(dp);
}
}
@@ -385,7 +385,7 @@ namespace Apewer.Source
var lower = column.Field.ToLower();
if (columns.Contains(lower)) continue;
- var type = GetColumnDeclaration(column);
+ var type = Declaration(column);
if (type.IsEmpty()) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。");
// alter table `_record` add column `_index` bigint;
@@ -407,7 +407,7 @@ namespace Apewer.Source
if (structure.Independent && column.Independent) continue;
// 字段。
- var type = GetColumnDeclaration(column);
+ var type = Declaration(column);
if (type.IsEmpty()) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。");
columns.Add(type);
columnsAdded++;
@@ -451,10 +451,11 @@ namespace Apewer.Source
}
// 连接数据库。
- if (!Connect())
+ var connect = Connect();
+ if (connect.NotEmpty())
{
sql = null;
- return "连接数据库失败。";
+ return $"连接数据库失败。({connect})";
}
sql = GetCreateStetement(structure);
@@ -476,24 +477,40 @@ namespace Apewer.Source
public string Initialize(Record model) => (model == null) ? "参数无效。" : Initialize(model.GetType());
/// 插入记录。返回错误信息。
- public string Insert(object record)
+ public string Insert(object record, string table = null)
{
if (record == null) return "参数无效。";
OrmHelper.FixProperties(record);
var structure = TableStructure.Parse(record.GetType());
if (structure == null) return "无法解析记录模型。";
+ if (string.IsNullOrEmpty(table)) table = structure.Name;
+ if (string.IsNullOrEmpty(table)) return "表名称无效。";
- var parameters = structure.CreateParameters(record, CreateDataParameter);
- var sql = GenerateInsertStatement(structure.Name, parameters);
- var execute = Execute(sql, parameters);
+ var ps = structure.CreateParameters(record, Parameter);
+ var psc = ps.Length;
+ if (psc < 1) return "数据模型不包含字段。";
+
+ var names = new List(psc);
+ var values = new List(psc);
+ foreach (var p in ps)
+ {
+ var pn = p.ParameterName;
+ names.Add("`" + p + "`");
+ values.Add("@" + p);
+ }
+ var ns = string.Join(", ", names);
+ var vs = string.Join(", ", values);
+ var sql = $"insert into `{table}` ({ns}) values ({vs}); ";
+
+ var execute = Execute(sql, ps);
if (execute.Success) return TextUtility.Empty;
return execute.Message;
}
/// 更新记录,实体中的 Key 属性不被更新。返回错误信息。
/// 无法更新带有 Independent 特性的模型(缺少 Key 属性)。
- public string Update(IRecord record)
+ public string Update(IRecord record, string table = null)
{
if (record == null) return "参数无效。";
FixProperties(record);
@@ -502,10 +519,23 @@ namespace Apewer.Source
var structure = TableStructure.Parse(record.GetType());
if (structure == null) return "无法解析记录模型。";
if (structure.Independent) return "无法更新带有 Independent 特性的模型。";
+ if (string.IsNullOrEmpty(table)) table = structure.Name;
+ if (string.IsNullOrEmpty(table)) return "表名称无效。";
+
+ var ps = structure.CreateParameters(record, Parameter, "_key");
+ var psc = ps.Length;
+ if (psc < 1) return "数据模型不包含字段。";
+
+ var items = new List(psc);
+ foreach (var p in ps)
+ {
+ var pn = p.ParameterName;
+ items.Add(TextUtility.Merge("`", pn, "` = @", pn));
+ }
+ var key = record.Key.SafeKey();
+ var sql = $"update `{table}` set {string.Join(", ", items)} where `_key`='{key}'; ";
- var parameters = structure.CreateParameters(record, CreateDataParameter, "_key");
- var sql = GenerateUpdateStatement(structure, record.Key, parameters);
- var execute = Execute(sql, parameters);
+ var execute = Execute(sql, ps);
if (execute.Success) return TextUtility.Empty;
return execute.Message;
}
@@ -514,7 +544,16 @@ namespace Apewer.Source
public Result
+
+
+
+
+
+
+
diff --git a/Apewer.Web/FavIcon.ico b/Apewer.Web/FavIcon.ico
new file mode 100644
index 0000000..0d7a022
Binary files /dev/null and b/Apewer.Web/FavIcon.ico differ
diff --git a/Apewer.Web/Internals/ApiHelper.cs b/Apewer.Web/Internals/ApiHelper.cs
index 23e4d8b..9927f37 100644
--- a/Apewer.Web/Internals/ApiHelper.cs
+++ b/Apewer.Web/Internals/ApiHelper.cs
@@ -400,8 +400,8 @@ namespace Apewer.Internals
internal static void Output(ApiProvider provider, ApiOptions options, string type, byte[] bytes)
{
- var preOutput = provider.PreWrite();
- if (!string.IsNullOrEmpty(preOutput)) return;
+ var preWrite = provider.PreWrite();
+ if (!string.IsNullOrEmpty(preWrite)) return;
var headers = PrepareHeaders(options, null);
foreach (var header in headers) provider.SetHeader(header.Key, header.Value);
@@ -416,8 +416,8 @@ namespace Apewer.Internals
internal static void Output(ApiProvider provider, ApiOptions options, ApiResponse response, ApiRequest request, HttpMethod method)
{
- var preOutput = provider.PreWrite();
- if (!string.IsNullOrEmpty(preOutput)) return;
+ var preWrite = provider.PreWrite();
+ if (!string.IsNullOrEmpty(preWrite)) return;
// 设置头。
var headers = PrepareHeaders(options, null);
diff --git a/Apewer.Web/Web/ApiEntries.cs b/Apewer.Web/Web/ApiEntries.cs
index 4101391..6c64137 100644
--- a/Apewer.Web/Web/ApiEntries.cs
+++ b/Apewer.Web/Web/ApiEntries.cs
@@ -127,6 +127,7 @@ namespace Apewer.Web
if (type.IsAbstract) return null;
if (type.IsGenericType) return null;
if (type.GetGenericArguments().NotEmpty()) return null;
+ if (!RuntimeUtility.CanNew(type)) return null;
// 检查类型的特性。
var apis = type.GetCustomAttributes(typeof(ApiAttribute), false);
diff --git a/Apewer.Web/Web/ApiProcessor.cs b/Apewer.Web/Web/ApiProcessor.cs
index f843e38..92f6d0f 100644
--- a/Apewer.Web/Web/ApiProcessor.cs
+++ b/Apewer.Web/Web/ApiProcessor.cs
@@ -60,9 +60,7 @@ namespace Apewer.Web
Provider.Options = Options;
// 检查执行的前提条件,获取 Method 和 URL。
- var check = Provider.PreInvoke();
- if (!string.IsNullOrEmpty(check)) return check;
- check = Check();
+ var check = Check();
if (!string.IsNullOrEmpty(check)) return check;
// 准备请求和响应模型。
diff --git a/Apewer.Web/Web/CronAttribute.cs b/Apewer.Web/Web/CronAttribute.cs
deleted file mode 100644
index 5e5a9f0..0000000
--- a/Apewer.Web/Web/CronAttribute.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Apewer.Web
-{
-
- /// Cron 特性。
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
- public sealed class CronAttribute : Attribute
- {
-
- internal const int DefaultInterval = 60000;
-
- private int _internval;
-
- /// 两次 Cron 执行的间隔毫秒数。
- public int Interval
- {
- get { return _internval; }
- private set { _internval = value < 1000 ? 1000 : value; }
- }
-
- /// 创建 Cron 特性,可指定两次 Cron 执行的间隔毫秒数。
- public CronAttribute(int interval = DefaultInterval)
- {
- Interval = interval;
- }
-
- }
-
-}
diff --git a/Apewer.Web/Web/CronInstance.cs b/Apewer.Web/Web/CronInstance.cs
deleted file mode 100644
index 78ebbee..0000000
--- a/Apewer.Web/Web/CronInstance.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-using Apewer;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading;
-
-namespace Apewer.Web
-{
-
- internal sealed class CronInstance
- {
-
- private Thread _thread = null;
- private Type _type = null;
- private bool _break = false;
- private bool _latest = false;
- private CronAttribute _attribute = null;
- private Nullable _ended = null;
-
- public CronInvoker Invoker { get; set; }
-
- public Thread Thread
- {
- get { return _thread; }
- }
-
- public bool Alive
- {
- get { return GetAlive(); }
- }
-
- /// 再次启动 Cron 的时间间隔。
- public int Interval
- {
- get { return GetInterval(); }
- }
-
- /// 最后一次检查的 Alive 值。
- public bool Latest
- {
- get { return _latest; }
- set { _latest = value; }
- }
-
- public Type Type
- {
- get { return _type; }
- set { _type = value; }
- }
-
- public bool Break
- {
- get { return _break; }
- set { _break = value; }
- }
-
- public CronAttribute Attribute
- {
- get { return _attribute; }
- set { _attribute = value; }
- }
-
- public Nullable Ended
- {
- get { return _ended; }
- set { _ended = value; }
- }
-
- public CronInstance()
- {
- _thread = new Thread(Listen);
- _thread.IsBackground = true;
- }
-
- void Log(params object[] content) => Invoker?.Log(content);
-
- public void Start()
- {
- if (Alive) return;
- _thread = new Thread(Listen);
- _thread.IsBackground = true;
- _thread.Start();
- }
-
- public void Abort()
- {
- if (_thread != null)
- {
- _thread.Abort();
- _thread = null;
- }
- }
-
- int GetInterval()
- {
- if (Attribute != null) return Attribute.Interval;
- return CronAttribute.DefaultInterval;
- }
-
- bool GetAlive()
- {
- if (_thread == null) return false;
- if (_thread.IsAlive != true) return false;
- if (Thread.ThreadState != ThreadState.Running) return false;
- return true;
- }
-
- void Listen()
- {
- if (Type == null) return;
- try
- {
- Activator.CreateInstance(Type);
- }
- catch (Exception exception)
- {
- Log(Type.FullName, exception.GetType().FullName, exception.Message);
- }
- _thread = null;
- }
-
- }
-
-}
diff --git a/Apewer.Web/Web/Resources.cs b/Apewer.Web/Web/Resources.cs
new file mode 100644
index 0000000..55c84b8
--- /dev/null
+++ b/Apewer.Web/Web/Resources.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+
+namespace Apewer.Web
+{
+
+ /// 程序集资源。
+ public static class Resources
+ {
+
+ static byte[] Bytes(string name)
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ using (var stream = assembly.GetManifestResourceStream(name)) return stream.Read();
+ }
+
+ static string Text(string name) => BytesUtility.WipeTextBom(Bytes(name)).Text();
+
+ /// 获取预置的 favicon.ico 文件,。
+ public static byte[] FavIcon() => Bytes("Apewer.FavIcon.ico");
+
+ /// 获取用于 .NET Framework 4.0 的 web.config 文件。
+ public static string WebConfig40() => Text("Apewer.WebConfig40.xml");
+
+ /// 获取用于 .NET Framework 4.6.1 的 web.config 文件。
+ public static string WebConfig461(bool netstandard = false) => Text(netstandard ? "Apewer.WebConfigStd.xml" : "Apewer.WebConfig461.xml");
+ }
+
+}
diff --git a/Apewer.Web/WebConfig40.xml b/Apewer.Web/WebConfig40.xml
new file mode 100644
index 0000000..6726e1e
--- /dev/null
+++ b/Apewer.Web/WebConfig40.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Apewer.Web/WebConfig461.xml b/Apewer.Web/WebConfig461.xml
new file mode 100644
index 0000000..16c6c0b
--- /dev/null
+++ b/Apewer.Web/WebConfig461.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Apewer.Web/WebConfigStd.xml b/Apewer.Web/WebConfigStd.xml
new file mode 100644
index 0000000..1649f27
--- /dev/null
+++ b/Apewer.Web/WebConfigStd.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Apewer/CronAttribute.cs b/Apewer/CronAttribute.cs
new file mode 100644
index 0000000..73a00e1
--- /dev/null
+++ b/Apewer/CronAttribute.cs
@@ -0,0 +1,83 @@
+using Apewer.Web;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+
+namespace Apewer
+{
+
+ /// Cron 特性,默认间隔为 60000 毫秒。
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+ public sealed class CronAttribute : Attribute
+ {
+
+ internal const int DefaultInterval = 60000;
+
+ private int _interval;
+
+ /// 两次 Cron 执行的间隔毫秒数。
+ public int Interval
+ {
+ get { return _interval; }
+ }
+
+ /// 创建 Cron 特性,可指定两次 Cron 执行的间隔毫秒数。
+ public CronAttribute(int interval = DefaultInterval)
+ {
+ _interval = interval;
+ }
+
+ #region CronInvoker
+
+ private static Class _invoker = new Class();
+
+ /// 开始 Cron 调用(不阻塞当前线程)。
+ ///
+ /// 参数
+ /// - assemblies: 包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索;
+ /// - logger: 日志记录程序,不指定此参数时将使用 Logger.Default。
+ ///
+ public static void Start(IEnumerable assemblies = null, Logger logger = null)
+ {
+ CronInvoker instance = null;
+ lock (_invoker)
+ {
+ if (_invoker) return;
+ instance = new CronInvoker();
+ _invoker.Value = instance;
+ }
+ instance.Logger = logger ?? Logger.Default;
+ instance.Load(assemblies ?? AppDomain.CurrentDomain.GetAssemblies());
+
+ Console.CancelKeyPress += (s, e) =>
+ {
+ Break();
+ e.Cancel = true;
+ };
+ instance.Start();
+ }
+
+ /// 在当前线程开始 Cron 调用(阻塞当前线程)。
+ ///
+ /// 参数
+ /// - assemblies: 包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索;
+ /// - logger: 日志记录程序,不指定此参数时将使用 Logger.Default。
+ ///
+ public static void Start(Logger logger, IEnumerable assemblies = null) => Start(assemblies, logger);
+
+ /// 打断 Cron 循环,不打断正在执行的 Cron。
+ public static void Break()
+ {
+ lock (_invoker)
+ {
+ if (!_invoker) return;
+ _invoker.Value.Break();
+ }
+ }
+
+ #endregion
+
+ }
+
+}
diff --git a/Apewer/CronInstance.cs b/Apewer/CronInstance.cs
new file mode 100644
index 0000000..c9f8829
--- /dev/null
+++ b/Apewer/CronInstance.cs
@@ -0,0 +1,95 @@
+using Apewer;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+
+namespace Apewer.Web
+{
+
+ internal sealed class CronInstance
+ {
+
+ internal bool _latest = false;
+ internal Type _type = null;
+ internal Logger _logger = null;
+ internal Class _ended = null;
+ internal CronAttribute _attribute = null;
+ internal CronInvoker _invoker = null;
+
+ private Thread _thread = null;
+ private bool _break = false;
+
+ #region properties
+
+ // 当前线程正在运行。
+ public bool Alive { get => GetAlive(); }
+
+ // 再次启动 Cron 的时间间隔。
+ public int Interval { get => GetInterval(); }
+
+ // 最后一次检查的 Alive 值。
+ public bool Latest { get => _latest; }
+
+ // Cron 类型。
+ public Type Type { get => _type; }
+
+ public CronAttribute Attribute { get => _attribute; }
+
+ public Class Ended { get => _ended; }
+
+ #endregion
+
+ public CronInstance()
+ {
+ _thread = new Thread(Listen);
+ _thread.IsBackground = true;
+ }
+
+ /// 打断循环。
+ public void Break() => _break = true;
+
+ /// 启动线程执行任务。
+ public void Start()
+ {
+ if (Alive) return;
+ _thread = new Thread(Listen);
+ _thread.IsBackground = true;
+ _thread.Start();
+ }
+
+ int GetInterval()
+ {
+ if (_attribute == null) _attribute = new CronAttribute();
+ return _attribute.Interval;
+ }
+
+ bool GetAlive()
+ {
+ if (_thread == null) return false;
+ if (_thread.IsAlive != true) return false;
+ if (_thread.ThreadState != ThreadState.Running) return false;
+ return true;
+ }
+
+ void Listen()
+ {
+ if (Type == null) return;
+ var instance = null as object;
+ try
+ {
+ instance = Activator.CreateInstance(Type);
+ }
+ catch (Exception exception)
+ {
+ Log(Type.FullName, exception.GetType().FullName, exception.Message);
+ }
+ RuntimeUtility.Dispose(instance);
+ _thread = null;
+ }
+
+ void Log(params object[] content) => _logger.Text(Type.FullName, content);
+
+ }
+
+}
diff --git a/Apewer.Web/Web/CronInvoker.cs b/Apewer/CronInvoker.cs
similarity index 73%
rename from Apewer.Web/Web/CronInvoker.cs
rename to Apewer/CronInvoker.cs
index 872c9ba..0a45106 100644
--- a/Apewer.Web/Web/CronInvoker.cs
+++ b/Apewer/CronInvoker.cs
@@ -9,7 +9,7 @@ namespace Apewer.Web
{
/// Cron 调度器。
- public sealed class CronInvoker
+ internal sealed class CronInvoker
{
#region Instance
@@ -17,12 +17,12 @@ namespace Apewer.Web
private List _assemblies = null;
private List _instances = null;
private bool _break = false;
- private Action _log = null;
+ private Logger _logger = null;
- /// 获取或设置 Log 处理程序。
- public Action LogAction { get { return _log; } set { _log = value; } }
+ /// 获取或设置日志记录器。
+ public Logger Logger { get { return _logger; } set { _logger = value; } }
- internal void Log(params object[] content) => Logger?.Text(typeof(CronInvoker), content);
+ private void Log(object content) => _logger?.Text("Cron", content);
/// 加载程序集。
public void Load(IEnumerable assemblies)
@@ -54,10 +54,7 @@ namespace Apewer.Web
}
/// 通知打断循环,所有 Cron 执行结束后退出。
- public void Break()
- {
- _break = true;
- }
+ public void Break() => _break = true;
/// 开始 Cron 调用。
public void Start()
@@ -79,39 +76,39 @@ namespace Apewer.Web
if (i.Alive) alive++;
if (_break)
{
- i.Break = true;
+ i.Break();
break;
}
// 当前线程正在活动。
if (i.Alive)
{
- i.Latest = true;
+ i._latest = true;
continue;
}
// 记录 Cron 结束时间,根据结束时间判断再次启动 Cron。
if (i.Latest)
{
- Log($"类型 {i.Type.FullName} 已结束。");
- i.Ended = DateTime.Now;
- i.Latest = false;
+ Log($"{i.Type.FullName} Ended");
+ i._ended = new Class(DateTime.Now);
+ i._latest = false;
}
if (i.Ended == null)
{
- Log($"准备开始类型 {i.Type.FullName}。");
+ Log($"{i.Type.FullName} Beginning");
i.Start();
- i.Latest = true;
+ i._latest = true;
}
else
{
var span = DateTime.Now - i.Ended.Value;
if (span.TotalMilliseconds >= Convert.ToDouble(i.Interval))
{
- Log($"准备开始类型 {i.Type.FullName}。");
+ Log($"{i.Type.FullName} Beginning");
i.Start();
- i.Latest = true;
+ i._latest = true;
}
}
}
@@ -121,7 +118,7 @@ namespace Apewer.Web
break;
}
- Thread.Sleep(1000);
+ Thread.Sleep(500);
GC.Collect();
}
@@ -139,9 +136,10 @@ namespace Apewer.Web
if (attribute == null) continue;
var instance = new CronInstance();
- instance.Invoker = this;
- instance.Attribute = attribute;
- instance.Type = type;
+ instance._invoker = this;
+ instance._attribute = attribute;
+ instance._type = type;
+ instance._logger = Logger;
list.Add(instance);
}
@@ -174,22 +172,12 @@ namespace Apewer.Web
#region Static
- /// 获取或设置日志记录器。
- public static Logger Logger { get; set; }
-
- /// 在当前线程开始 Cron 调用,可能会阻塞当前线程。可指定 Log 处理程序。
- public static CronInvoker Start(Assembly assembly, Action log = null)
- {
- var assemblies = new Assembly[] { assembly };
- return Start(assemblies);
- }
-
- /// 在当前线程开始 Cron 调用,可能会阻塞当前线程。可指定 Log 处理程序。
- public static CronInvoker Start(IEnumerable assemblies, Action log = null)
+ // 在当前线程开始 Cron 调用 。
+ public static CronInvoker Start(IEnumerable assemblies = null, Logger logger = null)
{
var instance = new CronInvoker();
- instance.LogAction = log;
- instance.Load(assemblies);
+ instance.Logger = logger;
+ instance.Load(assemblies ?? AppDomain.CurrentDomain.GetAssemblies());
instance.Start();
return instance;
}
diff --git a/Apewer/IndependentAttribute.cs b/Apewer/IndependentAttribute.cs
index 40a6c1f..f0bbde4 100644
--- a/Apewer/IndependentAttribute.cs
+++ b/Apewer/IndependentAttribute.cs
@@ -13,7 +13,7 @@ namespace Apewer
string _remark = null;
/// 无依赖特性。
- public IndependentAttribute(string remark) => _remark = remark;
+ public IndependentAttribute(string remark = null) => _remark = remark;
/// 备注。
public string Remark
diff --git a/Apewer/Json.cs b/Apewer/Json.cs
index 96de6a5..a2afe6c 100644
--- a/Apewer/Json.cs
+++ b/Apewer/Json.cs
@@ -9,6 +9,7 @@ using System.Dynamic;
using System.IO;
#endif
using System.Reflection;
+using System.Runtime.Serialization;
using System.Text;
namespace Apewer
@@ -1517,6 +1518,25 @@ namespace Apewer
}
}
+ static void Add(object entity, object item, int index)
+ {
+ try
+ {
+ if (entity is Array array)
+ {
+ array.SetValue(item, index);
+ }
+ else if (entity is IList list)
+ {
+ list.Add(entity);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (_throw) throw ex;
+ }
+ }
+
internal static void Array(object array, Json json, bool ignoreCase, string ignoreCharacters, bool force)
{
if (array == null) return;
@@ -1524,83 +1544,54 @@ namespace Apewer
if (json.TokenType != JTokenType.Array) return;
var type = array.GetType();
- var subtypes = type.GetGenericArguments();
- if (subtypes.Length < 1) return;
- var subtype = subtypes[0];
-
- var methods = type.GetMethods();
- var add = null as MethodInfo;
- foreach (var method in methods)
+ var subtype = null as Type;
+ if (array is Array)
{
- if (method.Name == "Add")
- {
- var parameters = method.GetParameters();
- if (parameters.Length == 1)
- {
- if (parameters[0].ParameterType.FullName == subtype.FullName)
- {
- add = method;
- break;
- }
- }
- }
+ string typeName = array.GetType().FullName.Replace("[]", string.Empty);
+ subtype = array.GetType().Assembly.GetType(typeName);
+ }
+ else
+ {
+ var subtypes = type.GetGenericArguments();
+ if (subtypes.Length < 1) return;
+ subtype = subtypes[0];
}
- if (add == null) return;
var jis = json.GetItems();
- foreach (var ji in jis)
- {
- var parameter = new object[1] { null };
- if (subtype.FullName == typeof(Json).FullName)
- {
- parameter[0] = ji;
- add.Invoke(array, parameter);
- }
+ for (var index = 0; index < jis.Length; index++)
+ {
+ var ji = jis[index];
+ if (subtype.Equals(typeof(Json))) Add(array, ji, index);
+ else if (subtype.Equals(typeof(string))) Add(array, (ji.TokenType == JTokenType.Null) ? null : ji.Text, index);
+ else if (subtype.Equals(typeof(byte))) Add(array, NumberUtility.Byte(ji.Text), index);
+ else if (subtype.Equals(typeof(short))) Add(array, NumberUtility.Int16(ji.Text), index);
+ else if (subtype.Equals(typeof(int))) Add(array, NumberUtility.Int32(ji.Text), index);
+ else if (subtype.Equals(typeof(long))) Add(array, NumberUtility.Int64(ji.Text), index);
+ else if (subtype.Equals(typeof(sbyte))) Add(array, NumberUtility.SByte(ji.Text), index);
+ else if (subtype.Equals(typeof(ushort))) Add(array, NumberUtility.UInt16(ji.Text), index);
+ else if (subtype.Equals(typeof(uint))) Add(array, NumberUtility.UInt32(ji.Text), index);
+ else if (subtype.Equals(typeof(ulong))) Add(array, NumberUtility.UInt64(ji.Text), index);
+ else if (subtype.Equals(typeof(float))) Add(array, NumberUtility.Single(ji.Text), index);
+ else if (subtype.Equals(typeof(double))) Add(array, NumberUtility.Double(ji.Text), index);
+ else if (subtype.Equals(typeof(decimal))) Add(array, NumberUtility.Decimal(ji.Text), index);
else
{
- switch (subtype.FullName)
+ var serializable = force ? true : CanSerialize(subtype, false);
+ if (serializable && (ji is Json))
{
- case "System.String":
- parameter[0] = (ji.TokenType == JTokenType.Null) ? null : ji.Text;
- add.Invoke(array, parameter);
- break;
- case "System.Int32":
- parameter[0] = NumberUtility.Int32(ji.Text);
- add.Invoke(array, parameter);
- break;
- case "System.Int64":
- parameter[0] = NumberUtility.Int64(ji.Text);
- add.Invoke(array, parameter);
- break;
- case "System.Double":
- parameter[0] = NumberUtility.Double(ji.Text);
- add.Invoke(array, parameter);
- break;
- case "System.Decimal":
- parameter[0] = NumberUtility.Decimal(ji.Text);
- add.Invoke(array, parameter);
- break;
- default:
- var serializable = force ? true : CanSerialize(subtype, false);
- if (serializable && (ji is Json))
- {
- switch (ji.TokenType)
- {
- case JTokenType.Object:
- var subobject = Activator.CreateInstance(subtype);
- Object(subobject, ji, ignoreCase, ignoreCharacters, force);
- parameter[0] = subobject;
- add.Invoke(array, parameter);
- break;
- case JTokenType.Array:
- var subarray = Activator.CreateInstance(subtype);
- Array(subarray, ji, ignoreCase, ignoreCharacters, force);
- parameter[0] = subarray;
- add.Invoke(array, parameter);
- break;
- }
- }
- break;
+ switch (ji.TokenType)
+ {
+ case JTokenType.Object:
+ var subobject = Activator.CreateInstance(subtype);
+ Object(subobject, ji, ignoreCase, ignoreCharacters, force);
+ Add(array, subobject, index);
+ break;
+ case JTokenType.Array:
+ var subarray = Activator.CreateInstance(subtype);
+ Array(subarray, ji, ignoreCase, ignoreCharacters, force);
+ Add(array, subarray, index);
+ break;
+ }
}
}
}
@@ -1615,6 +1606,7 @@ namespace Apewer
var setter = property.GetSetMethod();
if (setter == null) return;
+ var pt = property.PropertyType;
var ptname = property.PropertyType.FullName;
var parameter = new object[1] { null };
@@ -1665,7 +1657,8 @@ namespace Apewer
setter.Invoke(entity, parameter);
break;
default:
- var serializable = force ? true : CanSerialize(property.PropertyType, false);
+ var serializable = force;
+ if (!serializable) serializable = CanSerialize(property.PropertyType, false);
if (serializable && (value is Json))
{
switch (((Json)value).TokenType)
@@ -1677,7 +1670,17 @@ namespace Apewer
setter.Invoke(entity, parameter);
break;
case JTokenType.Array:
- var subarray = Activator.CreateInstance(property.PropertyType);
+ object subarray;
+ if (pt.BaseType != null && pt.BaseType.Equals(typeof(Array)))
+ {
+ subarray = new object();
+ var length = ((Json)value).GetItems().Length;
+ subarray = pt.InvokeMember("Set", BindingFlags.CreateInstance, null, subarray, new object[] { length });
+ }
+ else
+ {
+ subarray = Activator.CreateInstance(property.PropertyType);
+ }
Array(subarray, (Json)value, ignoreCase, ignoreCharacters, force);
parameter[0] = subarray;
setter.Invoke(entity, parameter);
@@ -1995,6 +1998,14 @@ namespace Apewer
{
if (type == null) return false;
+ if (type.BaseType.Equals(typeof(Array))) return true;
+
+ var interfaces = type.GetInterfaces();
+ foreach (var i in interfaces)
+ {
+ if (i.Equals(typeof(IList))) return true;
+ }
+
if (type.Equals(typeof(object))) return false;
var sas = type.GetCustomAttributes(typeof(SerializableAttribute), inherit);
if (sas != null && sas.Length > 0) return true;
diff --git a/Apewer/Logger.cs b/Apewer/Logger.cs
index cdc39c5..7c03a43 100644
--- a/Apewer/Logger.cs
+++ b/Apewer/Logger.cs
@@ -114,22 +114,22 @@ namespace Apewer
}
/// 记录错误。多个 Content 参数将以“ | ”分隔。
- internal void InnerError(object sender, params object[] content) => Colorful(sender, "Error", DarkRed, content, null, OnError);
+ internal void InnerError(object sender, object[] content) => Colorful(sender, "Error", DarkRed, content, null, OnError);
/// 记录警告。多个 Content 参数将以“ | ”分隔。
- internal void InnerWarning(object sender, params object[] content) => Colorful(sender, "Warning", DarkYellow, content, null, OnWarning);
+ internal void InnerWarning(object sender, object[] content) => Colorful(sender, "Warning", DarkYellow, content, null, OnWarning);
/// 记录警告。多个 Content 参数将以“ | ”分隔。
- internal void InnerInfo(object sender, params object[] content) => Colorful(sender, "Info", DarkBlue, content, null, OnInfo);
+ internal void InnerInfo(object sender, object[] content) => Colorful(sender, "Info", DarkBlue, content, null, OnInfo);
/// 记录文本。多个 Content 参数将以“ | ”分隔。
- internal void InnerText(object sender, params object[] content) => Colorful(sender, "Text", null, content, null, OnText);
+ internal void InnerText(object sender, object[] content) => Colorful(sender, "Text", null, content, null, OnText);
/// 记录调试。多个 Content 参数将以“ | ”分隔。
[Conditional("DEBUG")]
- internal void InnerDebug(object sender, params object[] content) => Colorful(sender, "Debug", null, content, null, OnDebug);
+ internal void InnerDebug(object sender, object[] content) => Colorful(sender, "Debug", null, content, null, OnDebug);
- private void Write(object sender, params object[] content) => Colorful(sender, null, null, content, null, null);
+ private void Write(object sender, object[] content) => Colorful(sender, null, null, content, null, null);
/// 创建新实例。
public Logger()
diff --git a/Apewer/NumberUtility.cs b/Apewer/NumberUtility.cs
index e36e227..25c1301 100644
--- a/Apewer/NumberUtility.cs
+++ b/Apewer/NumberUtility.cs
@@ -294,6 +294,26 @@ namespace Apewer
return default(decimal);
}
+ /// 获取布尔对象。
+ public static bool Boolean(object any)
+ {
+ if (any is bool _bool) return _bool;
+ if (any is byte _byte) return _byte == 1;
+ if (any is sbyte _sbyte) return _sbyte == 1;
+ if (any is short _short) return _short == 1;
+ if (any is ushort _ushort) return _ushort == 1;
+ if (any is int _int) return _int == 1;
+ if (any is uint _uint) return _uint == 1;
+ if (any is long _long) return _long == 1;
+ if (any is ulong _ulong) return _ulong == 1;
+ if (any is string _string)
+ {
+ _string = TextUtility.Lower(_string);
+ if (_string == "true" || _string == "yes" || _string == "y") return true;
+ }
+ return false;
+ }
+
/// 获取单精度浮点对象。
public static float Float(object number) => GetNumber(number, Convert.ToSingle, (v, d) => v / Convert.ToSingle(d));
diff --git a/Apewer/Source/ColumnAttribute.cs b/Apewer/Source/ColumnAttribute.cs
index c648e4d..ed2cb9f 100644
--- a/Apewer/Source/ColumnAttribute.cs
+++ b/Apewer/Source/ColumnAttribute.cs
@@ -17,7 +17,7 @@ namespace Apewer.Source
private PropertyInfo _property = null;
internal string PropertyName = null;
- private string _field = "";
+ private string _field = null;
private int _length = 0;
private ColumnType _type;
@@ -26,8 +26,7 @@ namespace Apewer.Source
private void Init(string field, ColumnType type, int length)
{
- if (string.IsNullOrEmpty(field)) field = TableStructure.RestrictName(field, string.IsNullOrEmpty(field));
- _field = string.IsNullOrEmpty(field) ? "" : TableStructure.RestrictName(field, string.IsNullOrEmpty(field));
+ _field = field;
_type = type;
switch (type)
{
@@ -103,7 +102,7 @@ namespace Apewer.Source
if (setter == null || setter.IsStatic) return null;
// 检查列名称。
- if (TextUtility.IsBlank(ca.Field)) ca._field = "_" + property.Name;
+ if (TextUtility.IsBlank(ca.Field)) ca._field = property.Name;
// 类型兼容。
var pt = property.PropertyType;
diff --git a/Apewer/Source/DbClient.cs b/Apewer/Source/DbClient.cs
new file mode 100644
index 0000000..375dd76
--- /dev/null
+++ b/Apewer/Source/DbClient.cs
@@ -0,0 +1,339 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Common;
+using System.Text;
+
+namespace Apewer.Source
+{
+
+ ///
+ abstract class DbClient
+ {
+
+ ///
+ public virtual Logger Logger { get; set; }
+
+ #region Connection
+
+ DbConnection _conn = null;
+ string _str = null;
+
+ ///
+ public Timeout Timeout { get; set; }
+
+ ///
+ public DbConnection Connection { get => _conn; }
+
+ ///
+ public bool Online { get => _conn == null ? false : (_conn.State == ConnectionState.Open); }
+
+ /// 连接字符串。
+ public string ConnectionString { get => _str; }
+
+ ///
+ public virtual string Connect()
+ {
+ if (_conn == null)
+ {
+ _str = GetConnectionString();
+ _conn = NewConnection();
+ _conn.ConnectionString = _str;
+ }
+ else
+ {
+ if (_conn.State == ConnectionState.Open) return null;
+ }
+
+ try
+ {
+ _conn.Open();
+ switch (_conn.State)
+ {
+ case ConnectionState.Open: return null;
+ default: return $"连接失败,当前处于 {_conn.State} 状态。";
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(this, "Connect", ex, _conn.ConnectionString);
+ Close();
+ return ex.Message;
+ }
+ }
+
+ ///
+ public void Close()
+ {
+ if (_conn != null)
+ {
+ if (_transaction != null)
+ {
+ if (_autocommit) Commit();
+ else Rollback();
+ }
+ _conn.Close();
+ _conn.Dispose();
+ _conn = null;
+ }
+ }
+
+ ///
+ public void Dispose() { Close(); }
+
+ #endregion
+
+ #region Transaction
+
+ private DbTransaction _transaction = null;
+ private bool _autocommit = false;
+
+ /// 启动事务。
+ public string Begin(bool commit = true) => Begin(commit, null);
+
+ /// 启动事务。
+ public string Begin(bool commit, Class isolation)
+ {
+ if (Connect() != null) return "未连接。";
+ if (_transaction != null) return "存在已启动的事务,无法再次启动。";
+ try
+ {
+ _transaction = isolation ? _conn.BeginTransaction(isolation.Value) : _conn.BeginTransaction();
+ _autocommit = commit;
+ return null;
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(this, "Commit", ex.Message());
+ return ex.Message();
+ }
+ }
+
+ /// 提交事务。
+ public string Commit()
+ {
+ if (_transaction == null) return "事务不存在。";
+ try
+ {
+ _transaction.Commit();
+ RuntimeUtility.Dispose(_transaction);
+ _transaction = null;
+ return null;
+ }
+ catch (Exception ex)
+ {
+ RuntimeUtility.Dispose(_transaction);
+ _transaction = null;
+ Logger.Error(this, "Commit", ex.Message());
+ return ex.Message();
+ }
+ }
+
+ /// 从挂起状态回滚事务。
+ public string Rollback()
+ {
+ if (_transaction == null) return "事务不存在。";
+ try
+ {
+ _transaction.Rollback();
+ RuntimeUtility.Dispose(_transaction);
+ _transaction = null;
+ return null;
+ }
+ catch (Exception ex)
+ {
+ RuntimeUtility.Dispose(_transaction);
+ _transaction = null;
+ Logger.Error(this, "Rollback", ex.Message());
+ return ex.Message();
+ }
+ }
+
+ #endregion
+
+ #region ADO
+
+ /// 查询。
+ public IQuery Query(string sql) => Query(sql, null);
+
+ /// 查询。
+ public IQuery Query(string sql, IEnumerable parameters)
+ {
+ if (TextUtility.IsBlank(sql)) return new Query(false, "语句无效。");
+ var connected = Connect();
+ if (connected.NotEmpty()) return new Query(false, connected);
+
+ try
+ {
+ using (var command = NewCommand())
+ {
+ command.Connection = _conn;
+ if (Timeout != null) command.CommandTimeout = Timeout.Query;
+ command.CommandText = sql;
+ if (parameters != null)
+ {
+ foreach (var parameter in parameters)
+ {
+ if (parameter != null) command.Parameters.Add(parameter);
+ }
+ }
+ using (var ds = new DataSet())
+ {
+ using (var da = NewDataAdapter(sql))
+ {
+ const string name = "result";
+ da.Fill(ds, name);
+ var table = ds.Tables[name];
+ return new Query(table, true);
+ }
+ }
+ }
+ }
+ catch (Exception exception)
+ {
+ Logger.Error(this, "Query", exception, sql);
+ return new Query(exception);
+ }
+ }
+
+ /// 执行。
+ public IExecute Execute(string sql) => Execute(sql, null);
+
+ /// 执行单条 Transact-SQL 语句,并加入参数。
+ public IExecute Execute(string sql, IEnumerable parameters)
+ {
+ if (TextUtility.IsBlank(sql)) return Example.InvalidExecuteStatement;
+
+ var connected = Connect();
+ if (connected.NotEmpty()) return new Execute(false, connected);
+
+ var inTransaction = _transaction != null;
+ if (!inTransaction) Begin();
+ try
+ {
+ using (var command = NewCommand())
+ {
+ command.Connection = _conn;
+ command.Transaction = (DbTransaction)_transaction;
+ if (Timeout != null) command.CommandTimeout = Timeout.Execute;
+ command.CommandText = sql;
+ if (parameters != null)
+ {
+ foreach (var parameter in parameters)
+ {
+ if (parameter != null) command.Parameters.Add(parameter);
+ }
+ }
+ var rows = command.ExecuteNonQuery();
+ if (!inTransaction) Commit(); // todo 此处应该检查事务提交产生的错误。
+ return new Execute(true, rows);
+ }
+ }
+ catch (Exception exception)
+ {
+ Logger.Error(this, "Execute", exception, sql);
+ if (!inTransaction) Rollback();
+ return new Execute(exception);
+ }
+ }
+
+ #endregion
+
+ #region ORM - Query
+
+ /// 查询记录。
+ /// 记录模型。
+ /// SQL 语句。
+ public Result Query(Type model, string sql)
+ {
+ if (_conn == null) return new Result("连接无效。");
+ if (model == null) return new Result("数据模型类型无效。");
+ if (string.IsNullOrEmpty(sql)) return new Result("SQL 语句无效。");
+
+ var query = Query(sql);
+ var result = null as Result;
+
+ if (query.Success)
+ {
+ try
+ {
+ var array = OrmHelper.Fill(query, model);
+ result = new Result(array);
+ }
+ catch (Exception ex)
+ {
+ result = new Result(ex);
+ }
+ }
+ else
+ {
+ result = new Result(query.Message);
+ }
+
+ query.Dispose();
+ return result;
+ }
+
+ ///
+ public Result Query(string sql) where T : class, new()
+ {
+ var query = Query(sql);
+ if (!query.Success) return new Result(query.Message);
+ var records = query.Fill();
+ query.Dispose();
+
+ var result = new Result(records);
+ return result;
+ }
+
+ #endregion
+
+ #region Static
+
+ /// 对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。
+ protected static string Escape(string text, int bytes = 0)
+ {
+ if (text.IsEmpty()) return "";
+
+ var t = text ?? "";
+ t = t.Replace("\\", "\\\\");
+ t = t.Replace("'", "\\'");
+ t = t.Replace("\n", "\\n");
+ t = t.Replace("\r", "\\r");
+ t = t.Replace("\b", "\\b");
+ t = t.Replace("\t", "\\t");
+ t = t.Replace("\f", "\\f");
+
+ if (bytes > 5)
+ {
+ if (t.Bytes(Encoding.UTF8).Length > bytes)
+ {
+ while (true)
+ {
+ t = t.Substring(0, t.Length - 1);
+ if (t.Bytes(Encoding.UTF8).Length <= (bytes - 4)) break;
+ }
+ t = t + " ...";
+ }
+ }
+
+ return t;
+ }
+
+ #endregion
+
+ ///
+ protected abstract string GetConnectionString();
+
+ ///
+ protected abstract DbConnection NewConnection();
+
+ ///
+ protected abstract DbDataAdapter NewDataAdapter(string sql);
+
+ ///
+ protected abstract DbCommand NewCommand();
+
+ }
+
+}
diff --git a/Apewer/Source/IDbClientAdo.cs b/Apewer/Source/IDbClientAdo.cs
index 83e5844..1fa4a40 100644
--- a/Apewer/Source/IDbClientAdo.cs
+++ b/Apewer/Source/IDbClientAdo.cs
@@ -18,8 +18,8 @@ namespace Apewer.Source
/// 数据库当前在线,表示连接可用。
bool Online { get; }
- /// 连接数据库,若未连接则尝试连接,获取连接成功的状态。
- bool Connect();
+ /// 连接数据库,若未连接则尝试连接,返回错误信息。
+ string Connect();
#endregion
diff --git a/Apewer/Source/IDbClientOrm.cs b/Apewer/Source/IDbClientOrm.cs
index f961360..0e6e2be 100644
--- a/Apewer/Source/IDbClientOrm.cs
+++ b/Apewer/Source/IDbClientOrm.cs
@@ -20,13 +20,15 @@ namespace Apewer.Source
/// 插入记录。
/// 要插入的记录实体。
+ /// 插入到指定表。当不指定时,由 record 类型决定。
/// 错误信息。当成功时候返回空字符串。
- public string Insert(object record);
+ public string Insert(object record, string table = null);
/// 更新记录。
/// 要插入的记录实体。
+ /// 插入到指定表。当不指定时,由 record 类型决定。
/// 错误信息。当成功时候返回空字符串。
- public string Update(IRecord record);
+ public string Update(IRecord record, string table = null);
/// 获取指定类型的主键,按 Flag 属性筛选。
/// 要查询的类型。
@@ -41,7 +43,7 @@ namespace Apewer.Source
/// 目标记录的类型。
/// 目标记录的主键。
/// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。
- public Result Get(Type model, string key, long flag = 0);
+ public Result Get(Type model, string key, long flag = 0);
/// 获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。
/// 目标记录的主键。
diff --git a/Apewer/Source/IRecordStamp.cs b/Apewer/Source/IRecordStamp.cs
index e44b945..da2b757 100644
--- a/Apewer/Source/IRecordStamp.cs
+++ b/Apewer/Source/IRecordStamp.cs
@@ -9,10 +9,10 @@ namespace Apewer.Source
public interface IRecordStamp
{
- /// 记录的创建时间 UTC 时间戳,值为执行 INSERT INTO 语句时的 UTC 时间戳。
+ /// 记录的创建时间,默认为本地时间。
long Created { get; set; }
- /// 记录的更新时间 UTC 时间戳,每次对此记录执行 UPDATE 时应更新此值为当前 UTC 时间戳。
+ /// 记录的更新时间,默认为本地时间。
long Updated { get; set; }
}
diff --git a/Apewer/Source/OrmHelper.cs b/Apewer/Source/OrmHelper.cs
index 7086974..28ab1b5 100644
--- a/Apewer/Source/OrmHelper.cs
+++ b/Apewer/Source/OrmHelper.cs
@@ -59,10 +59,10 @@ namespace Apewer.Source
#region IQuery -> IRecord
/// 读取所有行,生成列表。
- public static T[] Fill(IQuery query) where T : class, new() => As(Fill(query, typeof(T)));
+ public static T[] Fill(this IQuery query) where T : class, new() => As(Fill(query, typeof(T)));
/// 读取所有行填充到 T,组成 T[]。
- public static object[] Fill(IQuery query, Type model)
+ public static object[] Fill(this IQuery query, Type model)
{
if (query == null) return new object[0];
if (model == null) return new object[0];
@@ -76,7 +76,7 @@ namespace Apewer.Source
}
/// 获取指定列的所有值,无效值不加入结果。
- public static T[] Column(IQuery query, Func filler)
+ public static T[] Column(this IQuery query, Func filler)
{
if (query == null || filler == null) return new T[0];
@@ -105,7 +105,7 @@ namespace Apewer.Source
/// 将 Query 的行,填充到模型实体。
/// 填充失败时返回 NULL 值。
///
- public static IRecord Row(IQuery query, int rowIndex, Type model, TableStructure structure)
+ public static object Row(IQuery query, int rowIndex, Type model, TableStructure structure)
{
// 检查参数。
if (query == null || model == null || structure == null) return null;
@@ -210,7 +210,7 @@ namespace Apewer.Source
catch { }
}
}
- return record as IRecord;
+ return record;
}
#endregion
@@ -226,7 +226,7 @@ namespace Apewer.Source
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)
+ using (var query = database.Query(sql))
{
if (query == null) return new Result("查询实例无效。");
if (query.Table == null)
@@ -246,11 +246,11 @@ namespace Apewer.Source
}
}
- /// 查询记录。
- /// 记录模型。
- /// 数据库对象。
- /// SQL 语句。
- public static Result Query(IDbClientAdo database, string sql) where T : class, new() => As(Query(database, typeof(T), sql));
+ // /// 查询记录。
+ // /// 记录模型。
+ // /// 数据库对象。
+ // /// SQL 语句。
+ // public static Result Query(IDbClientAdo database, string sql) where T : class, new() => As(Query(database, typeof(T), sql));
/// 查询记录。
/// 数据库对象。
@@ -282,34 +282,35 @@ namespace Apewer.Source
/// 记录模型。
/// 主键。
/// 生成 SQL 语句的函数,传入参数为表名和主键值。
- public static Result Get(IDbClientAdo 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 语句获取函数无效。");
+ if (sqlGetter == null) return new Result("SQL 语句获取函数无效。");
var safetyKey = TextUtility.SafeKey(key);
- if (string.IsNullOrEmpty(safetyKey)) return new Result("主键无效。");
+ if (string.IsNullOrEmpty(safetyKey)) return new Result("主键无效。");
var query = null as IQuery;
- var record = null as IRecord;
+ var record = null as object;
try
{
+ record = Activator.CreateInstance(model);
var ts = TableStructure.Parse(model);
var tableName = ts.Name;
- if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。");
+ if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。");
var sql = sqlGetter(tableName, safetyKey);
query = database.Query(sql);
- if (query.Table == null) return new Result("没有获取到记录。");
+ if (query.Table == null) return new Result("没有获取到记录。");
record = Row(query, 0, model, ts);
}
catch (Exception ex)
{
RuntimeUtility.Dispose(query);
- return new Result(ex);
+ return new Result(ex);
}
RuntimeUtility.Dispose(query);
- if (record == null) return new Result("没有获取到记录。");
- return new Result(record);
+ if (record == null) return new Result("没有获取到记录。");
+ return new Result(record);
}
/// 获取具有指定主键的记录。
@@ -317,7 +318,7 @@ namespace Apewer.Source
/// 数据库对象。
/// 主键。
/// 生成 SQL 语句的函数,传入参数为表名和主键值。
- public static Result Get(IDbClientAdo database, string key, Func sqlGetter) where T : class, IRecord, new() => As(Get(database, typeof(T), key, sqlGetter));
+ public static Result Get(IDbClientAdo database, string key, Func sqlGetter) where T : class, IRecord, new() => As(Get(database, typeof(T), key, sqlGetter));
/// 获取主键。
/// 数据库对象。
@@ -398,20 +399,22 @@ namespace Apewer.Source
{
if (record == null) return;
- if (record is IRecord key) key.ResetKey();
+ if (record is IRecord key)
+ {
+ if (string.IsNullOrEmpty(key.Key)) key.ResetKey();
+ }
+ var now = DateTime.Now;
if (record is IRecordMoment moment)
{
- var now = ClockUtility.LucidNow;
- if (string.IsNullOrEmpty(moment.Created)) moment.Created = now;
- if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now;
+ if (string.IsNullOrEmpty(moment.Created)) moment.Created = now.Lucid();
+ if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now.Lucid();
}
-
if (record is IRecordStamp stamp)
{
var utc = ClockUtility.UtcStamp;
- if (stamp.Created == 0L) stamp.Created = utc;
- if (stamp.Updated == 0L) stamp.Updated = utc;
+ if (stamp.Created == 0L) stamp.Created = now.Stamp();
+ if (stamp.Updated == 0L) stamp.Updated = now.Stamp();
}
}
@@ -420,22 +423,19 @@ namespace Apewer.Source
public static bool SetUpdated(object record)
{
if (record == null) return false;
-
+ var now = DateTime.Now;
+ var setted = false;
if (record is IRecordMoment moment)
{
- var now = ClockUtility.LucidNow;
- moment.Updated = now;
- return true;
+ moment.Updated = now.Lucid();
+ setted = true;
}
-
if (record is IRecordStamp stamp)
{
- var utc = ClockUtility.UtcStamp;
- stamp.Updated = utc;
- return true;
+ stamp.Updated = now.Stamp();
+ setted = true;
}
-
- return false;
+ return setted;
}
#endregion
diff --git a/Apewer/Source/Record.cs b/Apewer/Source/Record.cs
index 45a46d3..b35f8b0 100644
--- a/Apewer/Source/Record.cs
+++ b/Apewer/Source/Record.cs
@@ -14,15 +14,19 @@ namespace Apewer.Source
public abstract class Record : IRecord
{
- const int KeyLength = 191;
+ const int KeyLength = 32;
private string _key = null;
private long _flag = 0;
/// 记录主键,一般使用 GUID 的字符串形式。
- /// 带有 Independent 特性的模型不包含此属性。
+ ///
+ /// 注:
+ /// 1. 默认长度为 32,需要修改长度时应该重写此属性;
+ /// 2. 带有 Independent 特性的模型不包含此属性。
+ ///
[Column("_key", ColumnType.NVarChar, KeyLength)]
- public string Key { get { return _key; } set { _key = Compact(value, KeyLength); } }
+ public virtual string Key { get { return _key; } set { _key = Compact(value, KeyLength); } }
/// 记录的标记,Int64 类型,区分记录的状态。
/// 带有 Independent 特性的模型不包含此属性。
@@ -30,7 +34,7 @@ namespace Apewer.Source
public long Flag { get { return _flag; } set { _flag = value; } }
/// 重置 Key 属性的值。
- public virtual void ResetKey() => _key = TextUtility.Key();
+ public virtual void ResetKey() => Key = TextUtility.Key();
///
public Record() => ResetKey();
diff --git a/Apewer/Source/TableAttribute.cs b/Apewer/Source/TableAttribute.cs
index b9bc15d..d92b33f 100644
--- a/Apewer/Source/TableAttribute.cs
+++ b/Apewer/Source/TableAttribute.cs
@@ -16,14 +16,14 @@ namespace Apewer.Source
public sealed class TableAttribute : Attribute
{
- private string _name;
- private string _store;
+ private string _name = null;
+ private string _store = null;
/// 标记表属性。
public TableAttribute(string name = null, string store = null)
{
- _name = TableStructure.RestrictName(name, string.IsNullOrEmpty(name));
- _store = string.IsNullOrEmpty(store) ? null : TableStructure.RestrictName(store, false);
+ _name = name;
+ _store = store;
}
/// 表名。
@@ -44,7 +44,7 @@ namespace Apewer.Source
private static Dictionary _tac = new Dictionary();
/// 解析表特性,默认使用缓存以提升性能。
- public static TableAttribute Parse(bool useCache = true) where T : IRecord => Parse(typeof(T), useCache);
+ public static TableAttribute Parse(bool useCache = true) where T: class, new() => Parse(typeof(T), useCache);
/// 解析表特性,默认使用缓存以提升性能。
public static TableAttribute Parse(Type type, bool useCache = true)
@@ -68,7 +68,7 @@ namespace Apewer.Source
if (tas.LongLength < 1L) return null;
var ta = (TableAttribute)tas[0];
- if (string.IsNullOrEmpty(ta.Name)) ta._name = "_" + type.Name;
+ if (string.IsNullOrEmpty(ta.Name)) ta._name = type.Name;
ta.Independent = RuntimeUtility.Contains(type, true);
if (useCache)
diff --git a/Apewer/Source/TableStructure.cs b/Apewer/Source/TableStructure.cs
index bdcff98..388766d 100644
--- a/Apewer/Source/TableStructure.cs
+++ b/Apewer/Source/TableStructure.cs
@@ -49,6 +49,9 @@ namespace Apewer.Source
/// 主键。
public ColumnAttribute Key { get => _key; }
+ /// 记录标记。
+ public ColumnAttribute Flag { get => _flag; }
+
/// 列信息。
public ColumnAttribute[] Columns { get => _columns; }
@@ -140,25 +143,33 @@ namespace Apewer.Source
#region TableAttribute
- /// 限定表名称/列名称。
- /// 名称。
- /// 名称以下划线开始。
- internal static string RestrictName(string name, bool startWithUnderline)
+ // 限定表名称/列名称。
+ static string RestrictName(string name, bool underline = false, bool english = false)
{
- if (name == null || name == Constant.EmptyString) return Constant.EmptyString;
- var lower = name.ToLower();
- var available = TextUtility.Merge("_", Constant.NumberCollection, Constant.LowerCollection);
- var sb = new StringBuilder();
- foreach (var c in lower)
+ if (string.IsNullOrEmpty(name)) return null;
+ var str = name;
+
+ // 限定名称仅使用英文和数字。
+ if (english)
+ {
+ const string available = "_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ var chars = new ArrayBuilder();
+ var strChars = str.ToCharArray();
+ var strLength = strChars.Length > 255 ? 255 : strChars.Length;
+ for (var i = 0; i < strLength; i++)
+ {
+ if (available.IndexOf(strChars[i]) > -1) chars.Add(strChars[i]);
+ }
+ str = new string(chars.Export());
+ }
+
+ // 以下划线开始。
+ if (underline)
{
- if (available.IndexOf(c) >= 0) sb.Append(c);
+ if (!str.StartsWith("_")) str = TextUtility.Merge("_", str);
}
- lower = sb.ToString();
- if (startWithUnderline && !lower.StartsWith("_")) lower = TextUtility.Merge("_", lower);
- while (lower.Length > 2 && lower.StartsWith("__")) lower = lower.Substring(1);
- if (lower == "_" || lower == Constant.EmptyString) return Constant.EmptyString;
- if (lower.Length > 255) lower = lower.Substring(0, 255);
- return lower;
+
+ return str;
}
static IDataParameter CreateParameter(object record, ColumnAttribute ca, Func callback)
diff --git a/Apewer/SystemUtility.cs b/Apewer/SystemUtility.cs
index 3ed9a88..2a92d78 100644
--- a/Apewer/SystemUtility.cs
+++ b/Apewer/SystemUtility.cs
@@ -22,6 +22,24 @@ namespace Apewer
#endif
+ ///
+ public static void SetConsoleCtrlCancel(Func exit)
+ {
+ const string postfix = " - 按 CTRL + C 可安全退出";
+ var title = Console.Title;
+ if (!title.EndsWith(postfix))
+ {
+ title = title + postfix;
+ Console.Title = title;
+ }
+
+ if (exit == null) return;
+ Console.CancelKeyPress += (s, e) =>
+ {
+ e.Cancel = !exit();
+ };
+ }
+
}
}
diff --git a/Apewer/TextUtility.cs b/Apewer/TextUtility.cs
index d2c465b..f8d044e 100644
--- a/Apewer/TextUtility.cs
+++ b/Apewer/TextUtility.cs
@@ -644,19 +644,22 @@ namespace Apewer
/// 返回此字符串的安全键副本,只保留数据记录主键中可能出现的字符,默认限制长度为 255 字符。
public static string SafeKey(string text, int maxLength = 255)
{
- if (string.IsNullOrEmpty(text)) return Constant.EmptyString;
- var input = Lower(text);
+ if (string.IsNullOrEmpty(text)) return Empty;
+ var input = text;
var max = maxLength;
if (max < 1 || max > input.Length) max = input.Length;
+ // 允许用于主键值的字符。
+ const string KeyCollection = "-_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
var sb = new StringBuilder();
var total = input.Length;
var length = 0;
for (var i = 0; i < total; i++)
{
var c = input[i];
- if (Constant.KeyCollection.IndexOf(c) < 0) continue;
+ if (KeyCollection.IndexOf(c) < 0) continue;
sb.Append(c);
length += 1;
if (length >= max) break;
diff --git a/Apewer/Web/ApiOptions.cs b/Apewer/Web/ApiOptions.cs
index 4244f8d..ac9c255 100644
--- a/Apewer/Web/ApiOptions.cs
+++ b/Apewer/Web/ApiOptions.cs
@@ -10,14 +10,14 @@ namespace Apewer.Web
public class ApiOptions
{
- /// 限制最大请求的字节数。
- /// 默认值:-1,不使用 ApiOptions 限制。
- public long MaxRequestBody { get; set; } = -1;
-
/// 设置 Access-Control-Max-Age 的值。
/// 默认值:60。
public int AccessControlMaxAge { get; set; } = 60;
+ /// 允许枚举输出 Applications 或 Functions。
+ /// 默认值:不允许,不输出列表。
+ public bool AllowEnumerate { get; set; } = false;
+
/// 允许解析 favicon.ico 请求。
/// 默认值:不允许,响应空。
public bool AllowFavIcon { get; set; } = false;
@@ -26,18 +26,42 @@ namespace Apewer.Web
/// 默认值:不允许,拒绝搜索引擎收录根目录。
public bool AllowRobots { get; set; } = false;
- /// 允许枚举输出 Applications 或 Functions。
- /// 默认值:不允许,不输出列表。
- public bool AllowEnumerate { get; set; } = false;
+ // /// 允许同步 IO。
+ // ///
+ // /// 默认值:允许。
+ // /// 允许:使用同步方法写入 Response.Body,可能会导致线程不足而崩溃。
+ // /// 不允许:必须用异步方法写入 Response.Body。
+ // ///
+ // public bool AllowSynchronousIO { get; set; } = true;
/// 允许输出的 Json 对象缩进。
/// 默认值:不缩进。
public bool JsonIndent { get; set; } = false;
+ /// 限制最大请求的字节数。
+ /// 默认值:-1,不使用 ApiOptions 限制。
+ public long MaxRequestBody { get; set; } = -1;
+
/// 在响应头中设置 Content-Security-Policy,要求浏览器升级资源链接,使用 HTTPS。
/// 默认值:不要求。在 HTTPS 页面中,不自动升级 HTTP 资源。
public bool UpgradeHttps { get; set; } = false;
+ /// 在响应中包含 Access-Control 属性。
+ /// 默认值:包含。
+ public bool WithAccessControl { get; set; } = true;
+
+ /// 在响应中包含时间属性。
+ /// 默认值:不包含。
+ public bool WithClock { get; set; } = false;
+
+ /// 允许响应标头中包含 X-Content-Type-Options: nosiff。
+ /// 默认值:不包含。当设置默认控制器时自动启用此属性。
+ public bool WithContentTypeOptions { get; set; } = false;
+
+ /// 在响应中包含执行 API 的持续时间。
+ /// 默认值:不包含。
+ public bool WithDuration { get; set; } = false;
+
/// 允许响应中包含 Exception 对象的属性。
/// 默认值:不允许。
public bool WithException { get; set; } = false;
@@ -50,37 +74,13 @@ namespace Apewer.Web
/// 默认值:不包含。
public bool WithParameters { get; set; } = false;
- /// 允许输出 Application 列表时包含类型名称。
- /// 默认值:不包含。
- public bool WithTypeName { get; set; } = false;
-
- /// 在响应中包含时间属性。
- /// 默认值:不包含。
- public bool WithClock { get; set; } = false;
-
- /// 在响应中包含执行 API 的持续时间。
- /// 默认值:不包含。
- public bool WithDuration { get; set; } = false;
-
/// 在响应中包含 Application 和 Function 属性。
/// 默认值:不包含。
public bool WithTarget { get; set; } = false;
- /// 在响应中包含 Access-Control 属性。
- /// 默认值:包含。
- public bool WithAccessControl { get; set; } = true;
-
- /// 允许响应标头中包含 X-Content-Type-Options: nosiff。
- /// 默认值:不包含。当设置默认控制器时自动启用此属性。
- public bool WithContentTypeOptions { get; set; } = false;
-
- /// 允许同步 IO。
- ///
- /// 默认值:允许。
- /// 允许:使用同步方法写入 Response.Body,可能会导致线程不足而崩溃。
- /// 不允许:必须用异步方法写入 Response.Body。
- ///
- public bool AllowSynchronousIO { get; set; } = true;
+ /// 允许输出 Application 列表时包含类型名称。
+ /// 默认值:不包含。
+ public bool WithTypeName { get; set; } = false;
#region 默认控制器,可用于静态控制器。
@@ -98,15 +98,22 @@ namespace Apewer.Web
#endregion
/// 创建默认选项。
- public ApiOptions()
- {
- Debug();
- }
+ ///
+ /// 在 Debug 模式中默认设置以下选项
+ ///
JsonIndent = TRUE
+ ///
WithException = TRUE
+ ///
WithDuration = TRUE
+ ///
WithParameters = TRUE
+ ///
+ public ApiOptions() => Debug();
[Conditional("DEBUG")]
void Debug()
{
+ JsonIndent = true;
WithException = true;
+ WithDuration = true;
+ WithParameters = true;
}
}
diff --git a/Apewer/Web/ApiProvider.cs b/Apewer/Web/ApiProvider.cs
index 91c82c7..a43b86c 100644
--- a/Apewer/Web/ApiProvider.cs
+++ b/Apewer/Web/ApiProvider.cs
@@ -16,13 +16,13 @@ namespace Apewer.Web
#region Implement
- /// 调用前的检查,可返回错误信息。
+ /// 调用前的检查,可返回错误信息以终止调用。
public virtual string PreInvoke() { return null; }
- /// 读取请求前的检查,可返回错误信息。
+ /// 读取请求前的检查,可返回错误信息以忽略 POST 内容。
public virtual string PreRead() { return null; }
- /// 写入响应前的检查,可返回错误信息。
+ /// 写入响应前的检查,可返回错误信息以终止输出。
public virtual string PreWrite() { return null; }
/// 结束本次请求和响应。
diff --git a/Apewer/_Common.props b/Apewer/_Common.props
index 01911ad..6a8502c 100644
--- a/Apewer/_Common.props
+++ b/Apewer/_Common.props
@@ -14,7 +14,7 @@
Apewer Libraries
- 6.4.2
+ 6.5.0
diff --git a/Apewer/_Extensions.cs b/Apewer/_Extensions.cs
index ff3ca5f..ecd808a 100644
--- a/Apewer/_Extensions.cs
+++ b/Apewer/_Extensions.cs
@@ -12,7 +12,7 @@ using System.Reflection;
using System.Text;
/// 扩展方法。
-public static class Extensions
+public static class Extensions_Apewer
{
#region Class Utility
@@ -77,22 +77,25 @@ public static class Extensions
#region String、StringBuilder
- /// 获取 Byte 对象。
+ /// 转换为 Boolean 值。
+ public static bool Boolean(this object @this) => NumberUtility.Boolean(@this);
+
+ /// 转换为 Byte 值。
public static byte Byte(this object @this) => NumberUtility.Byte(@this);
- /// 获取 Int32 对象。
+ /// 转换为 Int32 值。
public static int Int32(this object @this) => NumberUtility.Int32(@this);
- /// 获取 Int64 对象。
+ /// 转换为 Int64 值。
public static long Int64(this object @this) => NumberUtility.Int64(@this);
- /// 获取 Decimal 对象。
+ /// 转换为 Decimal 值。
public static decimal Decimal(this object @this) => NumberUtility.Decimal(@this);
- /// 获取单精度浮点对象。
+ /// 转换为单精度浮点值。
public static float Float(this object @this) => NumberUtility.Float(@this);
- /// 获取双精度浮点对象。
+ /// 转换为双精度浮点值。
public static double Double(this object @this) => NumberUtility.Double(@this);
/// 将文本转换为字节数组,默认使用 UTF-8。
@@ -288,20 +291,20 @@ public static class Extensions
public static void Exception(this Logger logger, object sender, Exception exception) => logger?.InnerException(sender, exception);
/// 记录错误。多个 Content 参数将以“ | ”分隔。
- public static void Error(this Logger logger, object sender, params object[] content) => logger?.InnerError(logger, sender, content);
+ public static void Error(this Logger logger, object sender, params object[] content) => logger?.InnerError(sender, content);
/// 记录警告。多个 Content 参数将以“ | ”分隔。
- public static void Warning(this Logger logger, object sender, params object[] content) => logger?.InnerWarning(logger, sender, content);
+ public static void Warning(this Logger logger, object sender, params object[] content) => logger?.InnerWarning(sender, content);
/// 记录警告。多个 Content 参数将以“ | ”分隔。
- public static void Info(this Logger logger, object sender, params object[] content) => logger?.InnerInfo(logger, sender, content);
+ public static void Info(this Logger logger, object sender, params object[] content) => logger?.InnerInfo(sender, content);
/// 记录文本。多个 Content 参数将以“ | ”分隔。
- public static void Text(this Logger logger, object sender, params object[] content) => logger?.InnerText(logger, sender, content);
+ public static void Text(this Logger logger, object sender, params object[] content) => logger?.InnerText(sender, content);
/// 记录调试。多个 Content 参数将以“ | ”分隔。
[Conditional("DEBUG")]
- public static void Debug(this Logger logger, object sender, params object[] content) => logger?.InnerDebug(logger, sender, content);
+ public static void Debug(this Logger logger, object sender, params object[] content) => logger?.InnerDebug(sender, content);
#endregion
diff --git a/ChangeLog.md b/ChangeLog.md
index e513361..1e330f9 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,6 +1,21 @@
### 最新提交
+### 6.5.0
+- Cron:新增 CronAttribute,取代 Source 中的 Cron,并支持 Dispose;
+- Json:修正 Json 转为 T[] 的问题;
+- Source:修正 FixProperties 强制重置 Key 的问题;
+- Source:Record 主键长度调整为 32;
+- Soruce:修正 ORM 的 Get 方法;
+- Source:不再默认添加下划线;
+- Source:Insert 和 Update 支持指定表名;
+- Source:SqlClient 支持解析表结构;
+- Source:去除 Table 和 Column 默认的下划线;
+- Source:Connect 现在返回错误信息;
+- Source:IRecordStamp 使用本地时间(原为 UTC 时间);
+- Web:ApiEntiry 增加 CanNew 检查;
+- Web:增加预置资源。
+
### 6.4.2
- ArrayBuilder:修正 512 长度无法扩展的问题。