diff --git a/Apewer/Apewer.csproj b/Apewer/Apewer.csproj
index dea55f6..583708e 100644
--- a/Apewer/Apewer.csproj
+++ b/Apewer/Apewer.csproj
@@ -2,41 +2,21 @@
-
+
+
- true
- bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
- latest
- CS0108,CS0162,CS0414,CS0612,CS0618,CS0649,CS1589,CS1570,CS1572,CS1573,CS3019,CS3021
Library
netcoreapp3.1;netstandard2.1;netstandard2.0;net461;net40;net20
-
- Elivo
- Apewer Lab
- Copyright Apewer Lab. All rights reserved.
-
- Apewer
- Apewer Libraries
- 6.3.3
-
-
-
-
Apewer
- true
- true
+
Apewer
+ Apewer
Apewer
-
-
- DEBUG;TRACE;$(DefineConstants);$(AdditionalConstants)
-
-
$(AssemblyName) - .NET Standard 2.1
diff --git a/Apewer/Logger.cs b/Apewer/Logger.cs
index cf55562..f5afb79 100644
--- a/Apewer/Logger.cs
+++ b/Apewer/Logger.cs
@@ -167,7 +167,7 @@ namespace Apewer
internal static object ConsoleLocker = new object();
/// 获取用于保存日志文件的路径。
- public static Func FilePathGetter { get; set; }
+ public static Func FilePathGetter { get; set; }
private static string MergeContent(params object[] content) => TextUtility.Join(" | ", content);
@@ -283,7 +283,7 @@ namespace Apewer
public static string GetFilePath(Logger logger = null)
{
var getter = FilePathGetter;
- if (getter != null) try { return getter.Invoke(); } catch { }
+ if (getter != null) try { return getter.Invoke(logger); } catch { }
// 找到 App_Data 目录。
var appDir = RuntimeUtility.ApplicationPath;
diff --git a/Apewer/Models/StringPairs.cs b/Apewer/Models/StringPairs.cs
index 08b1343..52cd3e0 100644
--- a/Apewer/Models/StringPairs.cs
+++ b/Apewer/Models/StringPairs.cs
@@ -12,6 +12,12 @@ namespace Apewer.Models
public class StringPairs : List>, IToJson
{
+ ///
+ public StringPairs() : base() { }
+
+ ///
+ public StringPairs(int capacity) : base(capacity) { }
+
/// 添加项。返回错误信息。
public string Add(string key, string value)
{
diff --git a/Apewer/Source/Accessor.cs b/Apewer/Source/Accessor.cs
deleted file mode 100644
index 1b49a88..0000000
--- a/Apewer/Source/Accessor.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Apewer.Source
-{
-
- ///
- public abstract class Accessor where T : class, IDatabase, new()
- {
-
- private T _source = null;
-
- private bool _disposed = false;
-
- ///
- public Accessor() { }
-
- ///
- protected virtual T Source
- {
- get
- {
- if (_source == null) _source = new T();
- return _source;
- }
- }
-
- ///
- public virtual bool Connected
- {
- get
- {
- return (_source == null) ? false : _source.Online;
- }
- }
-
- ///
- public virtual bool Disposed
- {
- get { return _disposed; }
- }
-
- ///
- public virtual void Close()
- {
- if (_source == null) return;
- _source.Close();
- }
-
- ///
- public virtual void Dispose()
- {
- if (_source != null)
- {
- _source.Dispose();
- _source = null;
- }
- _disposed = true;
- }
-
- }
-
-}
diff --git a/Apewer/Source/ColumnAttribute.cs b/Apewer/Source/ColumnAttribute.cs
index da82bf6..b5d5cab 100644
--- a/Apewer/Source/ColumnAttribute.cs
+++ b/Apewer/Source/ColumnAttribute.cs
@@ -21,105 +21,71 @@ namespace Apewer.Source
private bool _independent = false;
- private bool _locked = false;
-
- /// 使用自动的列名称。当类型为 VarChar 或 NVarChar 时必须指定长度。
///
- public ColumnAttribute(ColumnType type = ColumnType.NVarChar, int length = 191)
+ private void Init(string field, ColumnType type, int length, bool underline)
{
- New(null, type, length, true);
+ _field = string.IsNullOrEmpty(field) ? "" : TableStructure.RestrictName(field, underline);
+ _type = type;
+ switch (type)
+ {
+ case ColumnType.VarChar:
+ case ColumnType.NVarChar:
+ if (length < 1) throw new ArgumentException("最大长度无效。");
+ _length = length;
+ break;
+ case ColumnType.VarChar255:
+ case ColumnType.NVarChar255:
+ _length = 255;
+ break;
+ default:
+ _length = length;
+ break;
+ }
}
+ /// 使用自动的列名称。当类型为 VarChar 或 NVarChar 时必须指定长度。
+ ///
+ public ColumnAttribute(ColumnType type = ColumnType.NVarChar, int length = 191) => Init(null, type, length, true);
+
/// 使用指定的列名称。当类型为 VarChar 或 NVarChar 时必须指定长度。
///
- public ColumnAttribute(string field, ColumnType type = ColumnType.NVarChar, int length = 191)
- {
- New(field, type, length, false);
- }
+ 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)
- {
- New(field, type, length, underline);
- }
+ internal ColumnAttribute(string field, ColumnType type, int length, bool underline) => Init(field, type, length, underline);
/// 属性。
public PropertyInfo Property
{
- get { return _property; }
- internal set
- {
- if (_locked) return;
- _property = value;
- }
+ get => _property;
+ internal set => _property = value;
}
/// 字段名。
public string Field
{
- get { return _field; }
- set
- {
- if (_locked) return;
- _field = value;
- }
+ get => _field;
+ set => _field = value;
}
/// 指定字段的最大长度。
public int Length
{
- get { return _length; }
- set
- {
- if (_locked) return;
- _length = value;
- }
+ get => _length;
+ set => _length = value;
}
/// 字段类型。
public ColumnType Type
{
- get { return _type; }
- set
- {
- if (_locked) return;
- _type = value;
- }
+ get => _type;
+ set => _type = value;
}
/// Independent 特性。
public bool Independent
{
- get { return _independent; }
- internal set { _independent = value; }
- }
-
- ///
- private void New(string field, ColumnType type, int length, bool underline)
- {
- _field = string.IsNullOrEmpty(field) ? "" : TableStructure.RestrictName(field, underline);
- _type = type;
- switch (type)
- {
- case ColumnType.VarChar:
- case ColumnType.NVarChar:
- if (length < 1) throw new ArgumentException("最大长度无效。");
- _length = length;
- break;
- case ColumnType.VarChar255:
- case ColumnType.NVarChar255:
- _length = 255;
- break;
- default:
- _length = length;
- break;
- }
- Lock();
- }
-
- /// 锁定属性,阻止修改。
- public void Lock()
- {
- _locked = true;
+ get => _independent;
+ internal set => _independent = value;
}
}
diff --git a/Apewer/Source/Example.cs b/Apewer/Source/Example.cs
index ee84585..cff80dd 100644
--- a/Apewer/Source/Example.cs
+++ b/Apewer/Source/Example.cs
@@ -24,22 +24,22 @@ namespace Apewer.Source
}
///
- public static IExecute InvalidExecuteConnection { get { return CreateExecuteError("连接无效。"); } }
+ public static IExecute InvalidExecuteConnection => CreateExecuteError("连接无效。");
///
- public static IExecute InvalidExecuteStatement { get { return CreateExecuteError("语句无效。"); } }
+ public static IExecute InvalidExecuteStatement => CreateExecuteError("语句无效。");
///
- public static IExecute InvalidExecuteParameters { get { return CreateExecuteError("参数无效。"); } }
+ public static IExecute InvalidExecuteParameters => CreateExecuteError("参数无效。");
///
- public static IQuery InvalidQueryConnection { get { return CreateQueryError("连接无效。"); } }
+ public static IQuery InvalidQueryConnection => CreateQueryError("连接无效。");
///
- public static IQuery InvalidQueryStatement { get { return CreateQueryError("语句无效。"); } }
+ public static IQuery InvalidQueryStatement => CreateQueryError("语句无效。");
///
- public static IQuery InvalidQueryParameters { get { return CreateQueryError("参数无效。"); } }
+ public static IQuery InvalidQueryParameters => CreateQueryError("参数无效。");
}
diff --git a/Apewer/Source/IOrm.cs b/Apewer/Source/IOrm.cs
new file mode 100644
index 0000000..fb89a8f
--- /dev/null
+++ b/Apewer/Source/IOrm.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Apewer.Source
+{
+
+ /// 数据库引擎支持 ORM 访问。
+ public interface IOrm : IDatabase
+ {
+
+ /// 初始化指定类型,以创建表或增加字段。
+ /// 要初始化的类型。
+ /// 错误信息。当成功时候返回空字符串。
+ public string Initialize(Type model);
+
+ /// 插入记录。
+ /// 要插入的记录实体。
+ /// 错误信息。当成功时候返回空字符串。
+ public string Insert(Record record);
+
+ /// 更新记录。
+ /// 要插入的记录实体。
+ /// 错误信息。当成功时候返回空字符串。
+ public string Update(Record record);
+
+ /// 获取指定类型的主键,按 Flag 属性筛选。
+ /// 要查询的类型。
+ /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。
+ public Result> Keys(Type model, long flag = 0);
+
+ /// 获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。
+ /// 目标记录的主键。
+ /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。
+ public Result Get(string key, long flag = 0) where T : Record;
+
+ /// 使用指定语句查询,获取查询结果。
+ public Result> Query(string sql) where T : Record;
+
+ /// 查询所有记录。
+ /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。
+ public Result> Query(long flag = 0) where T : Record;
+
+ }
+
+}
diff --git a/Apewer/Source/MySql.cs b/Apewer/Source/MySql.cs
index 57a5b4f..0cdad6b 100644
--- a/Apewer/Source/MySql.cs
+++ b/Apewer/Source/MySql.cs
@@ -1,21 +1,28 @@
-#if NET40 || NET461
+#if (!EXTRA && (NET40 || NET461)) || (EXTRA && (NETSTD || NETCORE))
-/* 2021.02.20 */
+/* 2021.08.03 */
-using Externals.MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
+#if !EXTRA
+using Externals.MySql.Data.MySqlClient;
+#endif
+
+#if EXTRA
+using MySql.Data.MySqlClient;
+#endif
+
namespace Apewer.Source
{
///
- public class MySql : IDatabase, IDisposable
+ public class MySql : IDatabase, IOrm, IDisposable
{
- #region fields & properties
+#region fields & properties
private const string EmptyString = TextUtility.EmptyString;
@@ -63,9 +70,9 @@ namespace Apewer.Source
Pass = pass;
}
- #endregion
+#endregion
- #region 日志。
+#region 日志。
/// 获取或设置日志记录。
public Logger Logger { get; set; }
@@ -76,9 +83,9 @@ namespace Apewer.Source
if (logger != null) logger.Error(this, "MySQL", action, ex.GetType().FullName, ex.Message, addtion);
}
- #endregion
+#endregion
- #region methods
+#region methods
private string CombineString()
{
@@ -237,34 +244,34 @@ namespace Apewer.Source
///
public IExecute Execute(string sql) => Execute(sql, null as IEnumerable);
- #endregion
+#endregion
- #region linq
+#region ORM
- private List QueryFirstColumn(string sql)
+ private List FirstColumn(string sql)
{
using (var query = Query(sql) as Query) return query.ReadColumn();
}
///
- public List QueryAllTableNames()
+ public List TableNames()
{
var sql = TextUtility.Merge("select table_name from information_schema.tables where table_schema='", _store, "' and table_type='base table';");
- return QueryFirstColumn(sql);
+ return FirstColumn(sql);
}
///
- public List QueryAllViewNames()
+ public List ViewNames()
{
var sql = TextUtility.Merge("select table_name from information_schema.tables where table_schema='", _store, "' and table_type='view';");
- return QueryFirstColumn(sql);
+ return FirstColumn(sql);
}
///
- public List QueryAllColumnNames(string table)
+ public List ColumnNames(string table)
{
var sql = TextUtility.Merge("select column_name from information_schema.columns where table_schema='", _store, "' and table_name='", TextUtility.AntiInject(table), "';");
- return QueryFirstColumn(sql);
+ return FirstColumn(sql);
}
/// 获取用于创建表的语句。
@@ -272,7 +279,7 @@ namespace Apewer.Source
{
// 检查现存表。
var exists = false;
- var tables = QueryAllTableNames();
+ var tables = TableNames();
if (tables.Count > 0)
{
var lower = structure.Table.ToLower();
@@ -289,7 +296,7 @@ namespace Apewer.Source
if (exists)
{
- var columns = QueryAllColumnNames(structure.Table);
+ var columns = ColumnNames(structure.Table);
if (columns.Count > 0)
{
var lower = new List(columns.Count);
@@ -366,7 +373,7 @@ namespace Apewer.Source
}
///
- private string CreateTable(Type model, out string sql)
+ private string Initialize(Type model, out string sql)
{
if (model == null)
{
@@ -399,36 +406,25 @@ namespace Apewer.Source
}
///
- public string CreateTable(Type model)
- {
- return CreateTable(model, out string sql);
- }
+ public string Initialize(Type model) => Initialize(model, out string sql);
///
- public string CreateTable() where T : Record
- {
- return CreateTable(typeof(T));
- }
+ public string Initialize() where T : Record => Initialize(typeof(T));
///
- public string CreateTable(Record model)
- {
- if (model == null) return "参数无效。";
- return CreateTable(model.GetType());
- }
+ public string Initialize(Record model) => (model == null) ? "参数无效。" : Initialize(model.GetType());
/// 插入记录。成功时候返回空字符串,发生异常时返回异常信息。
- public string Insert(Record entity, bool resetKey = false)
+ public string Insert(Record record)
{
- if (entity == null) return "参数无效。";
- entity.FixProperties();
- if (resetKey) entity.ResetKey();
+ if (record == null) return "参数无效。";
+ record.FixProperties();
var structure = null as TableStructure;
- try { structure = TableStructure.ParseModel(entity); }
+ try { structure = TableStructure.ParseModel(record); }
catch (Exception exception) { return exception.Message; }
- var parameters = structure.CreateDataParameters(entity, CreateDataParameter);
+ var parameters = structure.CreateDataParameters(record, CreateDataParameter);
var sql = GenerateInsertStatement(structure.Table, parameters);
var execute = Execute(sql, parameters);
if (execute.Success) return TextUtility.EmptyString;
@@ -439,28 +435,27 @@ namespace Apewer.Source
/// 更新记录,实体中的 Created 和 Key 属性不被更新。成功时返回空字符串,发生异常时返回异常信息。
/// 无法更新拥有 Independent 特性的模型。
///
- public string Update(Record entity)
+ public string Update(Record record)
{
- if (entity == null) return "参数无效。";
- entity.FixProperties();
- entity.Updated = ClockUtility.LucidNow;
+ if (record == null) return "参数无效。";
+ record.FixProperties();
var structure = null as TableStructure;
- try { structure = TableStructure.ParseModel(entity); }
+ try { structure = TableStructure.ParseModel(record); }
catch (Exception exception) { return exception.Message; }
// 检查 Independent 特性。
if (structure.Independent) return "无法更新拥有 Independent 特性的模型。";
- var parameters = structure.CreateDataParameters(entity, CreateDataParameter, "_created", "_key");
- var sql = GenerateUpdateStatement(structure, entity.Key, parameters);
+ var parameters = structure.CreateDataParameters(record, CreateDataParameter, "_created", "_key");
+ var sql = GenerateUpdateStatement(structure, record.Key, parameters);
var execute = Execute(sql, parameters);
if (execute.Success) return TextUtility.EmptyString;
return execute.Error;
}
///
- public Result> QueryRecords(string sql) where T : Record
+ public Result> Query(string sql) where T : Record
{
if (sql.IsEmpty()) return new Result>(new ArgumentException());
try
@@ -480,7 +475,7 @@ namespace Apewer.Source
}
/// 获取记录。
- public Result QueryRecord(string key, long flag = 0) where T : Record
+ public Result Get(string key, long flag = 0) where T : Record
{
var k = TextUtility.SafeKey(key);
if (k.IsEmpty()) new Result(new Exception("参数无效。"));
@@ -490,7 +485,7 @@ namespace Apewer.Source
var sqlflag = (flag == 0) ? TextUtility.EmptyString : TextUtility.Merge(" `_flag`=", flag.ToString(), " and");
var sqlkey = TextUtility.Merge(" `_key`='", k, "'");
var sql = TextUtility.Merge("select * from `", structure.Table, "` where ", sqlflag, sqlkey, " limit 1;");
- var result = QueryRecords(sql);
+ var result = Query(sql);
var list = result.Entity;
if (list != null && list.Count > 0) return new Result(list[0]);
return new Result(null, "无结果。");
@@ -502,14 +497,14 @@ namespace Apewer.Source
}
/// 获取所有记录。Flag 为 0 时将忽略 Flag 条件。
- public Result> QueryRecords(long flag = 0) where T : Record
+ public Result> Query(long flag = 0) where T : Record
{
try
{
var structure = TableStructure.ParseModel(typeof(T));
var sqlflag = (flag == 0) ? TextUtility.EmptyString : TextUtility.Merge(" where `_flag`=", flag.ToString());
var sql = TextUtility.Merge("select * from `", structure.Table, "`", sqlflag, "; ");
- return QueryRecords(sql);
+ return Query(sql);
}
catch (Exception exception)
{
@@ -520,7 +515,7 @@ namespace Apewer.Source
/// 获取记录。
/// 要跳过的记录数,可用最小值为 0。
/// 要获取的记录数,可用最小值为 1。
- public Result> QueryRecords(int skip, int count) where T : Record
+ public Result> Query(int skip, int count) where T : Record
{
try
{
@@ -528,7 +523,7 @@ namespace Apewer.Source
if (count < 1) return new Result>(new ArgumentOutOfRangeException(nameof(count)));
var tableName = TableStructure.ParseTable(typeof(T)).Name;
var sql = $"select * from `{tableName}` where _flag = 1 limit {skip}, {count}; ";
- return QueryRecords(sql);
+ return Query(sql);
}
catch (Exception exception)
{
@@ -536,17 +531,17 @@ namespace Apewer.Source
}
}
- /// 查询有效的 Key 值。
- public Result> QueryKeys(Type model, long flag = 0)
+ /// >获取指定类型的主键,按 Flag 属性筛选。
+ public Result> Keys(Type model, long flag = 0)
{
if (model != null)
{
try
{
var type = model;
- var ts = TableStructure.ParseModel(type);
- var sqlflag = (flag == 0) ? TextUtility.EmptyString : TextUtility.Merge(" where `_flag`=", flag.ToString());
- var sql = TextUtility.Merge("select `_key` from `", ts.Table, "`", sqlflag, "; ");
+ var tn = TableStructure.ParseModel(type).Table;
+ var where = (flag == 0) ? TextUtility.EmptyString : TextUtility.Merge(" where `_flag`=", flag.ToString());
+ var sql = $"select `_key` from `{tn}`{where}; ";
using (var query = Query(sql) as Query)
{
var list = query.ReadColumn((r) => TextUtility.Trim(query.Text(r, 0)));
@@ -561,27 +556,8 @@ namespace Apewer.Source
return new Result>(new Exception("参数无效。"));
}
- /// 查询有效的 Key 值。
- public Result> QueryKeys(long flag = 0) where T : Record
- {
- try
- {
- var type = typeof(T);
-
- var ts = TableStructure.ParseModel(type);
- var sqlflag = (flag == 0) ? TextUtility.EmptyString : TextUtility.Merge(" where _flag=", flag.ToString());
- var sql = TextUtility.Merge("select `_key` from `", ts.Table, "`", sqlflag, "; ");
- using (var query = Query(sql) as Query)
- {
- var list = query.ReadColumn((r) => TextUtility.Trim(query.Text(r, 0)));
- return new Result>(list);
- }
- }
- catch (Exception ex)
- {
- return new Result>(ex);
- }
- }
+ /// >获取指定类型的主键,按 Flag 属性筛选。
+ public Result> Keys(long flag = 0) where T : Record => Keys(typeof(T), flag);
/// 对表添加列,返回错误信息。
/// 记录类型。
@@ -635,9 +611,9 @@ namespace Apewer.Source
return error;
}
- #endregion
+#endregion
- #region static
+#region static
/// 对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。
public static string Escape(string text, int bytes = 0)
@@ -996,7 +972,7 @@ namespace Apewer.Source
return result;
}
- #endregion
+#endregion
}
diff --git a/Apewer/Source/Record.cs b/Apewer/Source/Record.cs
index dfbb189..2c630dc 100644
--- a/Apewer/Source/Record.cs
+++ b/Apewer/Source/Record.cs
@@ -64,11 +64,11 @@ namespace Apewer.Source
if (record == null) return;
if (record.Flag == 0) record.Flag = 1;
- if (TextUtility.IsBlank(record.Key)) record.Key = TextUtility.NewGuid();
+ if (string.IsNullOrEmpty(record.Key)) record.Key = TextUtility.NewGuid();
var now = ClockUtility.LucidNow;
- if (TextUtility.IsBlank(record.Created)) record.Created = now;
- if (TextUtility.IsBlank(record.Updated)) record.Updated = now;
+ if (string.IsNullOrEmpty(record.Created)) record.Created = now;
+ if (string.IsNullOrEmpty(record.Updated)) record.Updated = now;
}
/// 枚举带有 Table 特性的 派生类型。
diff --git a/Apewer/Source/SqlServer.cs b/Apewer/Source/SqlServer.cs
index 09eebe8..ccd6b59 100644
--- a/Apewer/Source/SqlServer.cs
+++ b/Apewer/Source/SqlServer.cs
@@ -1,6 +1,6 @@
#if NET40 || NET461
-/* 2021.02.20 */
+/* 2021.08.03 */
using Apewer;
using Apewer.Source;
@@ -17,7 +17,7 @@ namespace Apewer.Source
/// 用于快速连接 Microsoft SQL Server 数据库的辅助。
[Serializable]
- public class SqlServer : IDatabase, IDisposable
+ public class SqlServer : IDatabase, IOrm, IDisposable
{
#region 变量定义。
@@ -322,7 +322,7 @@ namespace Apewer.Source
}
/// 查询数据库中的所有表名。
- public List QueryAllTableNames()
+ public List TableNames()
{
var list = new List();
if (Connect())
@@ -341,7 +341,7 @@ namespace Apewer.Source
}
/// 查询数据库实例中的所有数据库名。
- public List QueryAllStoreNames()
+ public List StoreNames()
{
var list = new List();
if (Connect())
@@ -364,7 +364,7 @@ namespace Apewer.Source
}
/// 查询表中的所有列名。
- public List QueryAllColumnNames(string tableName)
+ public List ColumnNames(string tableName)
{
var list = new List();
if (Connect())
@@ -384,16 +384,10 @@ namespace Apewer.Source
}
/// 创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。
- public string CreateTable(Record model)
- {
- if (model == null) return "参数无效。";
- var type = model.GetType();
- var error = CreateTable(type);
- return error;
- }
+ public string Initialize(Record model) => model == null ? "参数无效。" : Initialize(model);
/// 创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。
- public string CreateTable(Type model)
+ public string Initialize(Type model)
{
var structure = null as TableStructure;
try { structure = TableStructure.ParseModel(model); }
@@ -404,7 +398,7 @@ namespace Apewer.Source
// 检查现存表。
var exists = false;
- var tables = QueryAllTableNames();
+ var tables = TableNames();
if (tables.Count > 0)
{
var lower = structure.Table.ToLower();
@@ -422,7 +416,7 @@ namespace Apewer.Source
if (exists)
{
// 获取已存在的列名。
- var columns = QueryAllColumnNames(structure.Table);
+ var columns = ColumnNames(structure.Table);
if (columns.Count > 0)
{
var lower = new List();
@@ -478,18 +472,18 @@ namespace Apewer.Source
}
/// 插入记录。成功时候返回空字符串,发生异常时返回异常信息。
- public string Insert(Record entity)
+ public string Insert(Record record)
{
- if (entity == null) return "参数无效。";
- var type = entity.GetType();
+ if (record == null) return "参数无效。";
+ var type = record.GetType();
- entity.FixProperties();
+ record.FixProperties();
var structure = null as TableStructure;
- try { structure = TableStructure.ParseModel(entity); }
+ try { structure = TableStructure.ParseModel(record); }
catch (Exception exception) { return exception.Message; }
- var parameters = structure.CreateDataParameters(entity, CreateDataParameter);
+ var parameters = structure.CreateDataParameters(record, CreateDataParameter);
var sql = GenerateInsertStatement(structure.Table, parameters);
@@ -502,24 +496,24 @@ namespace Apewer.Source
/// 更新记录,实体中的 Created 和 Key 属性不被更新。成功时返回空字符串,发生异常时返回异常信息。
/// 无法更新拥有 Independent 特性的模型。
///
- public string Update(Record entity)
+ public string Update(Record record)
{
- if (entity == null) return "参数无效。";
- var type = entity.GetType();
+ if (record == null) return "参数无效。";
+ var type = record.GetType();
- entity.FixProperties();
- entity.Updated = ClockUtility.LucidNow;
+ record.FixProperties();
+ record.Updated = ClockUtility.LucidNow;
var structure = null as TableStructure;
- try { structure = TableStructure.ParseModel(entity); }
+ try { structure = TableStructure.ParseModel(record); }
catch (Exception exception) { return exception.Message; }
// 检查 Independent 特性。
if (structure.Independent) return "无法更新拥有 Independent 特性的模型。";
- var parameters = structure.CreateDataParameters(entity, CreateDataParameter, "_created", "_key");
+ var parameters = structure.CreateDataParameters(record, CreateDataParameter, "_created", "_key");
- var sql = GenerateUpdateStatement(structure.Table, entity.Key, parameters);
+ var sql = GenerateUpdateStatement(structure.Table, record.Key, parameters);
var execute = Execute(sql, parameters);
if (execute.Success) return TextUtility.EmptyString;
@@ -527,7 +521,7 @@ namespace Apewer.Source
}
/// 获取具有指定 Key 的记录。
- public Result QueryRecord(string key, long flag = 0) where T : Record
+ public Result Get(string key, long flag = 0) where T : Record
{
var k = TextUtility.SafeKey(key);
if (TextUtility.IsBlank(k) == false)
@@ -551,7 +545,7 @@ namespace Apewer.Source
}
/// 获取记录。
- public Result> QueryRecords(long flag = 0) where T : Record
+ public Result> Query(long flag = 0) where T : Record
{
try
{
@@ -588,7 +582,7 @@ namespace Apewer.Source
}
/// 查询有效的 Key 值。
- public Result> QueryKeys(Type model, long flag = 0)
+ public Result> Keys(Type model, long flag = 0)
{
if (model != null)
{
@@ -616,29 +610,7 @@ namespace Apewer.Source
}
/// 查询有效的 Key 值。
- public Result> QueryKeys(long flag = 0) where T : Record
- {
- try
- {
- var type = typeof(T);
-
- var ts = TableStructure.ParseModel(type);
- var f = (flag == 0) ? TextUtility.EmptyString : TextUtility.Merge(" where _flag=", flag.ToString());
- var query = (Query)Query(TextUtility.Merge("select _key from [", ts.Table, "]", f, "; "));
- var list = new List();
- for (var r = 0; r < query.Rows; r++)
- {
- var cell = TextUtility.Trim(query.Text(r));
- if (cell.Length > 0) list.Add(cell);
- }
- query.Dispose();
- return new Result>(list);
- }
- catch (Exception ex)
- {
- return new Result>(ex);
- }
- }
+ public Result> Keys(long flag = 0) where T : Record => Keys(typeof(T), flag);
#endregion
@@ -826,10 +798,7 @@ namespace Apewer.Source
}
/// 指定的连接凭据是否符合连接要求,默认指定 master 数据库。
- public static bool Proven(string address, string user, string pass)
- {
- return Proven(address, "master", user, pass);
- }
+ 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)
@@ -934,7 +903,7 @@ namespace Apewer.Source
/// 生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。
///
///
- public static string GenerateInsertStatement(string table, IEnumerable parameters)
+ private static string GenerateInsertStatement(string table, IEnumerable parameters)
{
if (table == null) throw new ArgumentNullException(nameof(table));
var tableName = TextUtility.AntiInject(table, 255);
@@ -949,7 +918,7 @@ namespace Apewer.Source
/// 生成 UPDATE 语句,键字段名为“_key”。表名必须有效,键值必须有效,无有效参数时将获取空结果。
///
///
- public static string GenerateUpdateStatement(string table, string key, IEnumerable parameters)
+ private static string GenerateUpdateStatement(string table, string key, IEnumerable parameters)
{
if (table == null) throw new ArgumentNullException(nameof(table));
var t = TextUtility.AntiInject(table, 255);
diff --git a/Apewer/Source/Sqlite.cs b/Apewer/Source/Sqlite.cs
new file mode 100644
index 0000000..d3a9c88
--- /dev/null
+++ b/Apewer/Source/Sqlite.cs
@@ -0,0 +1,965 @@
+#if EXTRA
+
+/* 2021.08.03 */
+
+using Apewer;
+using Apewer.Source;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.SQLite;
+using System.Text;
+using System.Data.Common;
+//using Mono.Data.Sqlite;
+
+namespace Apewer.Source
+{
+
+ /// 用于快速连接 SQLite 数据库的辅助。
+ public class Sqlite : IDatabase, IOrm, IDisposable
+ {
+
+ #region 变量定义。
+
+ private SQLiteConnection _db = null;
+
+ private Timeout _timeout = new Timeout();
+ private string _connstring = "";
+ private string _path = "";
+ private string _pass = "";
+ private byte[] _passdata = BinaryUtility.EmptyBytes;
+
+ #endregion
+
+ #region this
+
+ private void VarInit(string path, Timeout timeout, string pass, byte[] passData)
+ {
+ _path = (path == null) ? "" : path;
+ _passdata = (passData == null) ? BinaryUtility.EmptyBytes : passData;
+ _pass = (pass == null) ? "" : 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);
+
+ /// 连接指定文件。
+ private Sqlite(string path, Timeout timeout, byte[] pass) => VarInit(path, timeout, null, pass);
+
+ /// 连接指定文件。
+ public Sqlite(string path, Timeout timeout, string pass) => VarInit(path, timeout, pass, null);
+
+ #endregion
+
+ #region 日志。
+
+ /// 获取或设置日志记录。
+ 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);
+ }
+
+ private void LogError(string action, string message)
+ {
+ var logger = Logger;
+ if (logger != null) logger.Error(this, "SQLite", action, message);
+ }
+
+ #endregion
+
+ #region 实现接口。
+
+ /// 数据库是否已经连接。
+ public bool Online { get => _db != null && _db.State == ConnectionState.Open; }
+
+ /// 连接数据库,若未连接则尝试连接。
+ /// 是否已连接。
+ public bool Connect()
+ {
+ if (_db == null)
+ {
+ _db = new SQLiteConnection();
+ _db.ConnectionString = ConnectionString;
+ //if (string.IsNullOrEmpty(_connstring) && string.IsNullOrEmpty(_pass) && (_passdata.Length > 0))
+ //{
+ // _db.SetPassword(_pass);
+ //}
+ }
+ else
+ {
+ if (_db.State == ConnectionState.Open) return true;
+ }
+ try
+ {
+ _db.Open();
+ switch (_db.State)
+ {
+ case ConnectionState.Open: return true;
+ default: return false;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogError("Connection", ex, _db.ConnectionString);
+ Close();
+ return false;
+ }
+ }
+
+ /// 关闭连接,并释放对象所占用的系统资源。
+ public void Close()
+ {
+ if (_db != null)
+ {
+ //_db.Close();
+ _db.Dispose();
+ _db = null;
+ }
+ }
+
+ /// 关闭连接,释放对象所占用的系统资源,并清除连接信息。
+ public void Dispose() { Close(); }
+
+ /// 查询。
+ public IQuery Query(string sql) => Query(sql, null);
+
+ /// 查询。
+ public IQuery Query(string sql, IEnumerable parameters)
+ {
+ 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)
+ {
+ foreach (var p in parameters)
+ {
+ if (p != null) command.Parameters.Add(p);
+ }
+ }
+ using (var dataset = new DataSet())
+ {
+ using (var da = new SQLiteDataAdapter(sql, _db))
+ {
+ da.Fill(dataset, table);
+ query.Table = dataset.Tables[table];
+ }
+ }
+ command.Dispose();
+ query.Success = true;
+ }
+ catch (Exception ex)
+ {
+ LogError("Query", ex, sql);
+ query.Success = false;
+ query.Exception = ex;
+ }
+ return query;
+ }
+
+ /// 执行单条 Transact-SQL 语句。
+ public IExecute Execute(string sql) => Execute(sql, null);
+
+ /// 执行单条 Transact-SQL 语句,并加入参数。
+ public IExecute Execute(string sql, IEnumerable parameters)
+ {
+ if (string.IsNullOrEmpty(sql)) return Example.InvalidExecuteStatement;
+
+ var connected = Connect();
+ if (!connected) return Example.InvalidExecuteConnection;
+
+ var transaction = _db.BeginTransaction();
+ var execute = new Execute();
+ try
+ {
+ var command = new SQLiteCommand();
+ command.Connection = _db;
+ command.Transaction = transaction;
+ command.CommandTimeout = Timeout.Execute;
+ command.CommandText = sql;
+ if (parameters != null)
+ {
+ foreach (var p in parameters)
+ {
+ if (p != null) command.Parameters.Add(p);
+ }
+ }
+ 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;
+ }
+ try { transaction.Dispose(); } catch { }
+ return execute;
+ }
+
+ #endregion
+
+ #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) ? BinaryUtility.EmptyBytes : value;
+ }
+ }
+
+ /// 保存当前数据库到文件,若文件已存在则将重写文件。
+ public bool Save(string path)
+ {
+ if (!StorageUtility.CreateFile(path, 0, true))
+ {
+ LogError("Save", TextUtility.Merge("创建文件 ", path, " 失败。"));
+ return false;
+ }
+
+ var temp = new Sqlite(path);
+ var result = Save(temp);
+ temp.Close();
+ return result;
+ }
+
+ /// 保存当前数据库到文件,若文件已存在则将重写文件。
+ public bool Save(string path, string password)
+ {
+ if (!StorageUtility.CreateFile(path, 0, true))
+ {
+ LogError("Save", TextUtility.Merge("创建文件 ", path, " 失败。"));
+ return false;
+ }
+
+ var temp = new Sqlite(path, password);
+ var result = Save(temp);
+ temp.Close();
+ return result;
+ }
+
+ /// 保存当前数据库到目标数据库。
+ public bool Save(Sqlite destination)
+ {
+ var error = Backup(this, destination);
+ return string.IsNullOrEmpty(error);
+ }
+
+ /// 加载文件到当前数据库。
+ public bool Load(string path)
+ {
+ var temp = new Sqlite(path);
+ var result = Load(temp);
+ temp.Close();
+ return result;
+ }
+
+ /// 加载文件到当前数据库。
+ public bool Load(string path, params byte[] pass)
+ {
+ var temp = new Sqlite(path, pass);
+ var result = Load(temp);
+ temp.Close();
+ return result;
+ }
+
+ /// 加载文件到当前数据库。
+ public bool Load(string path, string pass)
+ {
+ var temp = new Sqlite(path, pass);
+ var result = Load(temp);
+ temp.Close();
+ return result;
+ }
+
+ /// 加载源数据库到当前数据库。
+ public bool Load(Sqlite source)
+ {
+ var error = Backup(source, this);
+ return string.IsNullOrEmpty(error);
+ }
+
+ #endregion
+
+ #region ORM。
+
+ /// 查询数据库中的所有表名。
+ public List TableNames()
+ {
+ var list = new List();
+ if (Connect())
+ {
+ var sql = "select name from sqlite_master where type='table' order by name; ";
+ var query = (Query)Query(sql);
+ for (int r = 0; r < query.Rows; r++)
+ {
+ var cell = query.Text(r, 0);
+ if (TextUtility.IsBlank(cell)) continue;
+ list.Add(cell);
+ }
+ query.Dispose();
+ }
+ return list;
+ }
+
+ /// 查询数据库中的所有视图名。
+ public List ViewNames()
+ {
+ var list = new List();
+ if (Connect())
+ {
+ var sql = "select name from sqlite_master where type='view' order by name; ";
+ var query = (Query)Query(sql);
+ for (int r = 0; r < query.Rows; r++)
+ {
+ var cell = query.Text(r, 0);
+ if (TextUtility.IsBlank(cell)) continue;
+ list.Add(cell);
+ }
+ query.Dispose();
+ }
+ return list;
+ }
+
+ /// 查询表中的所有列名。
+ public List ColumnNames(string table)
+ {
+ var list = new List();
+ if (Connect())
+ {
+ var t = TextUtility.AntiInject(table);
+ var sql = TextUtility.Merge("pragma table_info('", TextUtility.AntiInject(t), "'); ");
+ using (var query = Query(sql) as Query)
+ {
+ for (int r = 0; r < query.Rows; r++)
+ {
+ var cell = query.Text(r, "name");
+ if (TextUtility.IsBlank(cell)) continue;
+ list.Add(cell);
+ }
+ }
+ }
+ return list;
+ }
+
+ /// 创建表,不修改已存在表。成功时返回空字符串,发生异常时返回异常信息。
+ public string Initialize(Record model) => model == null ? "参数无效。" : Initialize(model.GetType());
+
+ /// 创建表,不修改已存在表。成功时返回空字符串,发生异常时返回异常信息。
+ public string Initialize() where T : Record => Initialize(typeof(T));
+
+ /// 创建表,不修改已存在表。当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。
+ public string Initialize(Type model)
+ {
+ var structure = null as TableStructure;
+ try { structure = TableStructure.ParseModel(model); }
+ catch (Exception exception) { return exception.Message; }
+
+ // 连接数据库。
+ if (!Connect()) return "连接数据库失败。";
+
+ // 检查现存表。
+ var exists = false;
+ var tables = TableNames();
+ if (tables.Count > 0)
+ {
+ var lower = structure.Table.ToLower();
+ foreach (var table in tables)
+ {
+ if (TextUtility.IsBlank(table)) continue;
+ if (table.ToLower() == lower)
+ {
+ exists = true;
+ break;
+ }
+ }
+ }
+
+ if (exists)
+ {
+ return TextUtility.Merge("指定的表已经存在。");
+ }
+ else
+ {
+ var sqlcolumns = new List();
+ foreach (var column in structure.Columns.Values)
+ {
+ var type = GetColumnDeclaration(column);
+ if (type == TextUtility.EmptyString) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。");
+ sqlcolumns.Add(type);
+ }
+ var sql = TextUtility.Merge("create table [", structure.Table, "](", TextUtility.Join(", ", sqlcolumns), "); ");
+ var execute = Execute(sql);
+ if (execute.Success) return TextUtility.EmptyString;
+ return execute.Error;
+ }
+ }
+
+ /// 插入记录。成功时候返回空字符串,发生异常时返回异常信息。
+ public string Insert(Record 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 execute = Execute(sql, parameters);
+ if (execute.Success && execute.Rows > 0) return TextUtility.EmptyString;
+ return execute.Error;
+ }
+
+ /// 更新记录,实体中的 Created 和 Key 属性不被更新。成功时返回空字符串,发生异常时返回异常信息。
+ public string Update(Record record)
+ {
+ if (record == null) return "参数无效。";
+ record.FixProperties();
+ record.Updated = ClockUtility.LucidNow;
+
+ 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 execute = Execute(sql, parameters);
+ if (execute.Success && execute.Rows > 0) return TextUtility.EmptyString;
+ return execute.Error;
+ }
+
+ /// 获取具有指定 Key 的记录。
+ public Result Get(string key) where T : Record => Get(key, 0);
+
+ /// 获取具有指定 Key 的记录。
+ public Result Get(string key, long flag) where T : Record
+ {
+ var k = TextUtility.SafeKey(key);
+ if (TextUtility.IsBlank(k) == false)
+ {
+ try
+ {
+ var ts = TableStructure.ParseModel(typeof(T));
+ var f = (flag == 0) ? TextUtility.EmptyString : TextUtility.Merge(" _flag=", flag.ToString(), " and");
+ var query = (Query)Query(TextUtility.Merge("select * from [", ts.Table, "] where", f, " _key='", k, "' limit 1; "));
+ var list = query.Fill();
+ query.Dispose();
+ if (list.Count > 0) return new Result(list[0]);
+ }
+ catch (Exception ex)
+ {
+ return new Result(ex);
+ }
+ }
+ return new Result(new Exception("参数无效。"));
+ }
+
+ /// 查询多条记录。
+ public Result> Query() where T : Record => Query(0);
+
+ /// 查询多条记录。
+ public Result> Query(long flag) where T : Record
+ {
+ try
+ {
+ var ts = TableStructure.ParseModel(typeof(T));
+ var f = (flag == 0) ? TextUtility.EmptyString : TextUtility.Merge(" where _flag=", flag.ToString());
+ var query = Query(TextUtility.Merge("select * from [", ts.Table, "]", f, "; ")) as Query;
+ var list = query.Fill();
+ query.Dispose();
+ return new Result>(list);
+ }
+ catch (Exception ex)
+ {
+ return new Result>(ex);
+ }
+ }
+
+ /// 获取按指定语句查询到的所有记录。
+ public Result> Query(string sql) where T : Record
+ {
+ using (var query = Query(sql) as Query)
+ {
+ if (query.Exception == null) return new Result>(query.Fill());
+ else return new Result>(query.Exception);
+ }
+ }
+
+ /// 查询所有有效的 Key 值。
+ public Result> Keys() where T : Record => Keys(0);
+
+ /// 查询所有有效的 Key 值。
+ public Result> Keys(long flag) where T : Record => Keys(typeof(T), flag);
+
+ /// 查询所有有效的 Key 值。
+ public Result> Keys(Type model) => Keys(model, 0);
+
+ /// 查询所有有效的 Key 值。
+ public Result> Keys(Type model, long flag)
+ {
+ if (model != null)
+ {
+ try
+ {
+ var list = new List();
+ var ts = TableStructure.ParseModel((Type)model);
+ var f = (flag == 0) ? TextUtility.EmptyString : TextUtility.Merge(" where _flag=", flag.ToString());
+ var query = (Query)Query(TextUtility.Merge("select _key from [", ts.Table, "]", f, "; "));
+ for (var r = 0; r < query.Rows; r++)
+ {
+ var cell = TextUtility.Trim(query.Text(r));
+ if (cell.Length > 0) list.Add(cell);
+ }
+ query.Dispose();
+ return new Result>(list);
+ }
+ catch (Exception ex)
+ {
+ return new Result>(ex);
+ }
+ }
+ return new Result>(new Exception("参数无效。"));
+ }
+
+ #endregion
+
+ #region static
+
+ /// 对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。
+ public static string Escape(string text, int bytes = 0)
+ {
+ if (text.IsEmpty()) return "";
+
+ var t = text ?? "";
+ t = t.Replace("\\", "\\\\");
+ t = t.Replace("'", "\\'");
+ t = t.Replace("\n", "\\n");
+ t = t.Replace("\r", "\\r");
+ t = t.Replace("\b", "\\b");
+ t = t.Replace("\t", "\\t");
+ t = t.Replace("\f", "\\f");
+
+ if (bytes > 5)
+ {
+ if (t.GetBytes(Encoding.UTF8).Length > bytes)
+ {
+ while (true)
+ {
+ t = t.Substring(0, t.Length - 1);
+ if (t.GetBytes(Encoding.UTF8).Length <= (bytes - 4)) break;
+ }
+ t = t + " ...";
+ }
+ }
+
+ return t;
+ }
+
+ private static string GetColumnTypeName(ColumnType type)
+ {
+ switch (type)
+ {
+ case ColumnType.Binary:
+ return "blob";
+ case ColumnType.Integer:
+ return "integer";
+ case ColumnType.Float:
+ return "float";
+ case ColumnType.VarChar:
+ case ColumnType.VarChar255:
+ case ColumnType.VarCharMax:
+ return "varchar";
+ case ColumnType.Text:
+ return "text";
+ case ColumnType.NVarChar:
+ case ColumnType.NVarChar255:
+ case ColumnType.NVarCharMax:
+ return "nvarchar";
+ case ColumnType.NText:
+ return "ntext";
+ default:
+ return null;
+ }
+ }
+
+ private static string GetColumnDeclaration(ColumnAttribute column)
+ {
+ var type = TextUtility.EmptyString;
+ var length = NumberUtility.RestrictValue(column.Length, 0, 255).ToString();
+ switch (column.Type)
+ {
+ case ColumnType.Binary:
+ type = "blob";
+ break;
+ case ColumnType.Integer:
+ type = "integer";
+ break;
+ case ColumnType.Float:
+ type = "real";
+ break;
+ case ColumnType.VarChar:
+ case ColumnType.VarChar255:
+ type = TextUtility.Merge("varchar(", length, ")");
+ break;
+ case ColumnType.VarCharMax:
+ type = TextUtility.Merge("varchar(255)");
+ break;
+ case ColumnType.Text:
+ type = TextUtility.Merge("text");
+ break;
+ case ColumnType.NVarChar:
+ case ColumnType.NVarChar255:
+ type = TextUtility.Merge("nvarchar(", length, ")");
+ break;
+ case ColumnType.NVarCharMax:
+ type = TextUtility.Merge("nvarchar(255)");
+ break;
+ case ColumnType.NText:
+ type = TextUtility.Merge("ntext");
+ break;
+ default:
+ return TextUtility.EmptyString;
+ }
+ return TextUtility.Merge("[", (object)column.Field, "] ", type);
+ }
+
+ /// 创建参数。
+ ///
+ ///
+ public static SQLiteParameter CreateDataParameter(Parameter parameter)
+ {
+ if (parameter == null) throw new InvalidOperationException("参数无效。");
+ return CreateDataParameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value);
+ }
+
+ /// 创建参数。
+ public static SQLiteParameter CreateDataParameter(string name, ColumnType type, int size, object value)
+ {
+ var n = TextUtility.Trim(name);
+ if (TextUtility.IsBlank(n)) return null;
+
+ var t = GetColumnTypeName(type);
+
+ var s = size;
+ switch (type)
+ {
+ case ColumnType.VarChar:
+ s = NumberUtility.RestrictValue(s, 0, 8000);
+ break;
+ case ColumnType.NVarChar:
+ s = NumberUtility.RestrictValue(s, 0, 4000);
+ break;
+ case ColumnType.VarChar255:
+ case ColumnType.VarCharMax:
+ case ColumnType.NVarChar255:
+ case ColumnType.NVarCharMax:
+ s = NumberUtility.RestrictValue(s, 0, 255);
+ break;
+ default:
+ s = 0;
+ break;
+ }
+
+ var v = value;
+ if (v is string && v != null && s > 0)
+ {
+ v = TextUtility.RestrictLength((string)v, s);
+ }
+
+ var p = new SQLiteParameter();
+ p.ParameterName = n;
+ p.TypeName = t;
+ p.Value = v;
+ if (s > 0) p.Size = s;
+ return p;
+ }
+
+ /// 创建参数。
+ public static SQLiteParameter CreateDataParameter(string name, string type, int size, object value)
+ {
+ var v = value;
+ if (value is string && value != null && size > 0)
+ {
+ v = TextUtility.RestrictLength((string)value, size);
+ }
+
+ var p = new SQLiteParameter();
+ p.ParameterName = name;
+ p.TypeName = type;
+ p.Value = v;
+ p.Size = size;
+ return p;
+ }
+
+ /// 创建参数。
+ public static SQLiteParameter CreateDataParameter(string name, string type, object value)
+ {
+ var p = new SQLiteParameter();
+ p.ParameterName = name;
+ p.TypeName = type;
+ p.Value = value;
+ return p;
+ }
+
+ /// 备份数据库,返回错误信息。
+ public static string Backup(Sqlite source, Sqlite destination)
+ {
+ if (source == null) return "SQLite Backup Failed: Invalid Source";
+ if (destination == null) return "SQLite Backup Failed: Invalid Destination";
+ if (!source.Connect()) return "SQLite Backup Failed: Invalid Source Connection";
+ if (!destination.Connect()) return "SQLite Backup Failed: Invalid Destination Connection";
+
+ try
+ {
+ source._db.BackupDatabase(destination._db, "main", "main", -1, null, 0);
+ return "";
+ }
+ catch (Exception ex)
+ {
+ return "SQLite Load Failed: " + ex.Message;
+ }
+ }
+
+ /// 创建参数。
+ public static IDbDataParameter CreateParameter(string field, DbType type, int size, object value)
+ {
+ var p = new SQLiteParameter();
+ p.ParameterName = field;
+ p.DbType = type;
+ p.Size = size;
+ p.Value = value;
+ return p;
+ }
+
+ /// 创建参数。
+ public static IDbDataParameter CreateParameter(string field, DbType type, object value)
+ {
+ var p = new SQLiteParameter();
+ p.ParameterName = field;
+ p.DbType = type;
+ p.Value = value;
+ return p;
+ }
+
+ /// 整理数据库,压缩未使用的空间。
+ public const string Vacuum = "vacuum";
+
+ /// 内存数据库的地址。
+ public const string Memory = ":memory:";
+
+ #endregion
+
+ #region ORM
+
+ private static string GetParameterName(string parameter)
+ {
+ var name = TextUtility.AntiInject(parameter, 255);
+ if (name.StartsWith("@") && name.Length > 1)
+ {
+ name = name.Substring(1, name.Length - 1);
+ }
+ return name;
+ }
+
+ private static string GetParameterName(IDataParameter parameter)
+ {
+ var name = TextUtility.EmptyString;
+ if (parameter != null)
+ {
+ name = GetParameterName(parameter.ParameterName);
+ }
+ return name;
+ }
+
+ private static List GetParametersNames(IEnumerable parameters)
+ {
+ var columns = new List();
+ if (parameters != null)
+ {
+ foreach (var parameter in parameters)
+ {
+ var name = GetParameterName(parameter);
+ var isblank = TextUtility.IsBlank(name);
+ if (isblank) continue;
+ columns.Add(name);
+ }
+ }
+ return columns;
+ }
+
+ private static string GenerateInsertStatement(string table, List columns)
+ {
+ var r = TextUtility.EmptyString;
+ var t = TextUtility.AntiInject(table, 255);
+ if (columns != null && !TextUtility.IsBlank(t))
+ {
+ var count = 0;
+ var names = new List();
+ var values = new List();
+ foreach (var column in columns)
+ {
+ //names.Add(TextGenerator.Merge("[", column, "]"));
+ names.Add(TextUtility.Merge(column));
+ values.Add("@" + column);
+ count += 1;
+ }
+ var sb = new StringBuilder();
+ if (count > 0)
+ {
+ sb.Append("insert into [", t, "](", TextUtility.Join(", ", names), ") ");
+ sb.Append("values(", TextUtility.Join(", ", values), "); ");
+ }
+ r = sb.ToString();
+ }
+ 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.EmptyString;
+
+ return GenerateInsertStatement(t, cs);
+ }
+
+ private static string GenerateUpdateStatement(string table, string key, List columns)
+ {
+ var result = TextUtility.EmptyString;
+ var t = TextUtility.AntiInject(table, 255);
+ var k = TextUtility.AntiInject(key, 255);
+ if (columns != null && !TextUtility.IsBlank(t) && !TextUtility.IsBlank(k))
+ {
+ var items = new List();
+ foreach (var column in columns)
+ {
+ items.Add(TextUtility.Merge("[", column, "]=@", column));
+ }
+ if (items.Count > 0)
+ {
+ result = TextUtility.Merge("update [", t, "] set ", TextUtility.Join(", ", items), " where [_key]='", k, "'; ");
+ }
+ }
+ return result;
+ }
+
+ /// 生成 UPDATE 语句,键字段名为“_key”。表名必须有效,键值必须有效,无有效参数时将获取空结果。
+ ///
+ ///
+ public static string GenerateUpdateStatement(string table, string key, IEnumerable parameters)
+ {
+ if (table == null) throw new ArgumentNullException("argTable");
+ var t = TextUtility.AntiInject(table, 255);
+ if (TextUtility.IsBlank(t)) throw new ArgumentException("表名无效。", nameof(table));
+
+ if (key == null) throw new ArgumentNullException("argKey");
+ var k = TextUtility.AntiInject(key, 255);
+ if (TextUtility.IsBlank(k)) throw new ArgumentException("键值无效。", nameof(key));
+
+ var columns = GetParametersNames(parameters);
+ if (columns.Count < 1) return TextUtility.EmptyString;
+
+ return GenerateUpdateStatement(t, k, columns);
+ }
+
+ #endregion
+
+ }
+
+}
+
+#endif
diff --git a/Apewer/Source/TableAttribute.cs b/Apewer/Source/TableAttribute.cs
index 0bbbf63..7b5124d 100644
--- a/Apewer/Source/TableAttribute.cs
+++ b/Apewer/Source/TableAttribute.cs
@@ -15,8 +15,6 @@ namespace Apewer.Source
private string _name;
private bool _independent = false;
- private bool _locked = false;
-
///
public TableAttribute(string name = null)
{
@@ -32,36 +30,19 @@ namespace Apewer.Source
/// 表名。
public string Name
{
- get { return _name; }
- set
- {
- if (_locked) return;
- _name = TableStructure.RestrictName(value, false);
- }
+ get => _name;
+ set => _name = TableStructure.RestrictName(value, false);
}
///
public bool Independent
{
- get { return _independent; }
- internal set
- {
- if (_locked) return;
- _independent = value;
- }
+ get => _independent;
+ internal set => _independent = value;
}
///
- public override int GetHashCode()
- {
- return _name.GetHashCode();
- }
-
- /// 锁定属性,阻止修改。
- public void Lock()
- {
- _locked = true;
- }
+ public override int GetHashCode() => _name.GetHashCode();
}
diff --git a/Apewer/Source/TableStructure.cs b/Apewer/Source/TableStructure.cs
index 956ce1b..a89b28c 100644
--- a/Apewer/Source/TableStructure.cs
+++ b/Apewer/Source/TableStructure.cs
@@ -14,8 +14,6 @@ namespace Apewer.Source
public sealed class TableStructure
{
- private bool _locked = false;
-
private string _tablename = Constant.EmptyString;
private bool _independent = false;
@@ -26,38 +24,22 @@ namespace Apewer.Source
/// 不依赖 Record 公共属性。
public bool Independent
{
- get { return _independent; }
- private set { _independent = value; }
+ get => _independent;
+ private set => _independent = value;
}
/// 表名称。
public string Table
{
- get { return _tablename; }
- private set { _tablename = value ?? ""; }
+ get => _tablename;
+ private set => _tablename = value ?? "";
}
/// 列信息。
public Dictionary Columns
{
- get
- {
- if (_locked)
- {
- var copy = new Dictionary(_columns.Count);
- foreach (var c in _columns) copy.Add(c.Key, c.Value);
- return copy;
- }
- return _columns;
- }
- private set { _columns = value; }
- }
-
- /// 锁定属性,阻止修改。
- public void Lock()
- {
- _locked = true;
- foreach (var c in _columns) c.Value.Lock();
+ get => _columns;
+ private set => _columns = value;
}
#region cache
@@ -157,9 +139,6 @@ namespace Apewer.Source
ts.Independent = ta.Independent;
ts.Columns = columns;
- // 锁定属性。
- ts.Lock();
-
// 加入缓存。
if (useCache)
{
@@ -216,9 +195,6 @@ namespace Apewer.Source
ta.Independent = RuntimeUtility.ContainsAttribute(type, true);
- // 锁定属性。
- ta.Lock();
-
// 加入缓存。
if (useCache)
{
@@ -291,9 +267,6 @@ namespace Apewer.Source
ca.Property = property;
- // 锁定属性。
- ca.Lock();
-
return ca;
}
diff --git a/Apewer/Web/ApiMime.cs b/Apewer/Web/ApiMime.cs
deleted file mode 100644
index f9e1ae3..0000000
--- a/Apewer/Web/ApiMime.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Apewer.Web
-{
-
- ///
- [Serializable]
- public sealed class ApiMime
- {
-
- ///
- public string Extension { get; set; }
-
- ///
- public string ContentType { get; set; }
-
- /// 缓存过期时间,单位为秒,默认值 0 为不缓存(立即过期)。
- public int Expires { get; set; }
-
- ///
- public ApiMime() { }
-
- internal ApiMime(string ext, string type, int expires)
- {
- Extension = ext;
- ContentType = type;
- Expires = expires;
- }
-
- }
-
-}
diff --git a/Apewer/Web/ApiOptions.cs b/Apewer/Web/ApiOptions.cs
index 5cee1c5..d563047 100644
--- a/Apewer/Web/ApiOptions.cs
+++ b/Apewer/Web/ApiOptions.cs
@@ -9,31 +9,35 @@ namespace Apewer.Web
public static class ApiOptions
{
- /// 允许 Invoker 解析 favicon.ico 请求。
+ /// 允许解析 favicon.ico 请求。
/// 默认值:不允许,响应空。
public static bool AllowFavIcon { get; set; } = false;
- /// 允许 Invoker 解析 robots.txt 请求。
+ /// 允许解析 robots.txt 请求。
/// 默认值:不允许,拒绝搜索引擎收录根目录。
public static bool AllowRobots { get; set; } = false;
- /// 允许 Invoker 枚举输出 Applications 或 Functions。
+ /// 允许响应标头中包含 X-Content-Type-Options: nosiff。
+ /// 默认值:不包含。当设置默认控制器时自动启用此属性。
+ public static bool AllowContentTypeOptions { get; set; } = false;
+
+ /// 允许枚举输出 Applications 或 Functions。
/// 默认值:不允许,不输出列表。
public static bool AllowEnumerate { get; set; } = false;
- /// 允许 Invoker 输出 Exception 对象的属性。
+ /// 允许输出 Exception 对象的属性。
/// 默认值:不允许输出。
public static bool AllowException { get; set; } = false;
- /// 允许 Invoker 输出的 Json 对象缩进。
+ /// 允许输出的 Json 对象缩进。
/// 默认值:不缩进。
public static bool JsonIndent { get; set; } = false;
- /// 允许 Invoker 输出 Application 列表时包含模块名称。
+ /// 允许输出 Application 列表时包含模块名称。
/// 默认值:不包含。
public static bool WithModuleName { get; set; } = false;
- /// 允许 Invoker 输出 Application 列表时包含类型名称。
+ /// 允许输出 Application 列表时包含类型名称。
/// 默认值:不包含。
public static bool WithTypeName { get; set; } = false;
diff --git a/Apewer/Web/ApiProcessor.cs b/Apewer/Web/ApiProcessor.cs
index 89a51c6..a88b8ba 100644
--- a/Apewer/Web/ApiProcessor.cs
+++ b/Apewer/Web/ApiProcessor.cs
@@ -140,18 +140,13 @@ namespace Apewer.Web
// 检查执行的前提条件。
string PreCheck()
{
- // Context
+ // 检查条件。
if (!HaveContext) return "Context 无效。";
-
- // Entries
if (Entries == null) return "Entries 无效。";
-
- // Method
Method = GetMethod();
if (Method == HttpMethod.NULL) return "Methods 无效。";
- // AccessControl
- // 在此之后可以输出
+ // 响应标头。
if (ApiOptions.WithAccessControl)
{
AddHeader("Access-Control-Allow-Headers", "Content-Type");
@@ -161,6 +156,12 @@ namespace Apewer.Web
var maxage = ApiOptions.AccessControlMaxAge;
if (maxage > 0) AddHeader("Access-Control-Max-Age", maxage.ToString());
}
+ if (ApiOptions.AllowContentTypeOptions || ApiOptions.Default != null)
+ {
+ AddHeader("X-Content-Type-Options", "nosniff");
+ }
+
+ // 在此之后可以输出
// URL
Url = GetURL();
@@ -419,6 +420,11 @@ namespace Apewer.Web
SetCacheControl(apiResponse.Expires);
Output(apiResponse.BinaryStream, type);
}
+ else if (!string.IsNullOrEmpty(apiResponse.BinaryPath))
+ {
+ SetCacheControl(apiResponse.Expires);
+ Output(StorageUtility.ReadFile(apiResponse.BinaryPath), type);
+ }
return null;
}
diff --git a/Apewer/Web/ApiResponse.cs b/Apewer/Web/ApiResponse.cs
index 556019d..9efbfeb 100644
--- a/Apewer/Web/ApiResponse.cs
+++ b/Apewer/Web/ApiResponse.cs
@@ -68,6 +68,8 @@ namespace Apewer.Web
internal byte[] BinaryBytes;
+ internal string BinaryPath;
+
internal string BinaryType;
#endregion
diff --git a/Apewer/Web/StaticController.cs b/Apewer/Web/StaticController.cs
index 71301d8..a9de0ef 100644
--- a/Apewer/Web/StaticController.cs
+++ b/Apewer/Web/StaticController.cs
@@ -113,11 +113,12 @@ namespace Apewer.Web
var html = ReadWithSSI(path);
var bytes = html.ToBinary();
Response.Binary(bytes, type);
- return;
}
-
- var stream = StorageUtility.OpenFile(path, true);
- Response.Binary(stream, type);
+ else
+ {
+ var bytes = StorageUtility.OpenFile(path, true);
+ Response.Binary(bytes, type);
+ }
}
void ExecuteDirectory(string path)
@@ -305,6 +306,27 @@ namespace Apewer.Web
{
var lower = extension.ToLower();
switch (lower)
+ {
+ case "css": return "text/css; charset=utf-8";
+ case "htm": return "text/html; charset=utf-8";
+ case "html": return "text/html; charset=utf-8";
+ case "js": return "application/javascript; charset=utf-8";
+ // case "json": return "application/json";
+ case "json": return "text/json; charset=utf-8";
+ case "shtml": return "text/html; charset=utf-8";
+ }
+ switch (lower)
+ {
+ // case "m3u8": return "application/vnd.apple.mpegurl";
+ case "m3u8": return "text/vnd.apple.mpegurl";
+ case "txt": return "text/plain";
+ case "xml": return "text/xml";
+ case "htc": return "text/x-component";
+ case "jad": return "text/vnd.sun.j2me.app-descriptor";
+ case "mml": return "text/mathml";
+ case "wml": return "text/vnd.wap.wml";
+ }
+ switch (lower)
{
case "3gp": return "video/3gpp";
case "3gpp": return "video/3gpp";
@@ -318,7 +340,6 @@ namespace Apewer.Web
case "bmp": return "image/x-ms-bmp";
case "cco": return "application/x-cocoa";
case "crt": return "application/x-x509-ca-cert";
- case "css": return "text/css";
case "deb": return "application/octet-stream";
case "der": return "application/x-x509-ca-cert";
case "dll": return "application/octet-stream";
@@ -332,32 +353,22 @@ namespace Apewer.Web
case "flv": return "video/x-flv";
case "gif": return "image/gif";
case "hqx": return "application/mac-binhex40";
- case "htc": return "text/x-component";
- case "htm": return "text/html";
- case "html": return "text/html";
case "ico": return "image/x-icon";
case "img": return "application/octet-stream";
case "iso": return "application/octet-stream";
- case "jad": return "text/vnd.sun.j2me.app-descriptor";
case "jar": return "application/java-archive";
case "jardiff": return "application/x-java-archive-diff";
case "jng": return "image/x-jng";
case "jnlp": return "application/x-java-jnlp-file";
case "jpeg": return "image/jpeg";
case "jpg": return "image/jpeg";
- case "js": return "application/javascript";
- // case "json": return "application/json";
- case "json": return "text/json";
case "kar": return "audio/midi";
case "kml": return "application/vnd.google-earth.kml+xml";
case "kmz": return "application/vnd.google-earth.kmz";
- // case "m3u8": return "application/vnd.apple.mpegurl";
- case "m3u8": return "text/vnd.apple.mpegurl";
case "m4a": return "audio/x-m4a";
case "m4v": return "video/x-m4v";
case "mid": return "audio/midi";
case "midi": return "audio/midi";
- case "mml": return "text/mathml";
case "mng": return "video/x-mng";
case "mov": return "video/quicktime";
case "mp3": return "audio/mpeg";
@@ -389,7 +400,6 @@ namespace Apewer.Web
case "rtf": return "application/rtf";
case "run": return "application/x-makeself";
case "sea": return "application/x-sea";
- case "shtml": return "text/html";
case "sit": return "application/x-stuffit";
case "svg": return "image/svg+xml";
case "svgz": return "image/svg+xml";
@@ -399,12 +409,10 @@ namespace Apewer.Web
case "tiff": return "image/tiff";
case "tk": return "application/x-tcl";
case "ts": return "video/mp2t";
- case "txt": return "text/plain";
case "war": return "application/java-archive";
case "wbmp": return "image/vnd.wap.wbmp";
case "webm": return "video/webm";
case "webp": return "image/webp";
- case "wml": return "text/vnd.wap.wml";
case "wmlc": return "application/vnd.wap.wmlc";
case "wmv": return "video/x-ms-wmv";
case "woff": return "font/woff";
@@ -412,7 +420,6 @@ namespace Apewer.Web
case "xhtml": return "application/xhtml+xml";
case "xls": return "application/vnd.ms-excel";
case "xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
- case "xml": return "text/xml";
case "xpi": return "application/x-xpinstall";
case "xspf": return "application/xspf+xml";
case "zip": return "application/zip";
diff --git a/Apewer/Web/WebUtility.cs b/Apewer/Web/WebUtility.cs
index c541b3b..4a96707 100644
--- a/Apewer/Web/WebUtility.cs
+++ b/Apewer/Web/WebUtility.cs
@@ -547,6 +547,7 @@ namespace Apewer.Web
response.Type = ApiFormat.Binary;
response.BinaryStream = null;
response.BinaryBytes = content;
+ response.BinaryPath = null;
response.BinaryType = type ?? "application/octet-stream";
}
@@ -557,6 +558,18 @@ namespace Apewer.Web
response.Type = ApiFormat.Binary;
response.BinaryStream = content;
response.BinaryBytes = null;
+ response.BinaryPath = null;
+ response.BinaryType = type ?? "application/octet-stream";
+ }
+
+ /// 输出二进制。
+ public static void SetBinary(ApiResponse response, string path, string type = "application/octet-stream")
+ {
+ if (response == null) return;
+ response.Type = ApiFormat.Binary;
+ response.BinaryStream = null;
+ response.BinaryBytes = null;
+ response.BinaryPath = path;
response.BinaryType = type ?? "application/octet-stream";
}
diff --git a/Apewer/_Common.props b/Apewer/_Common.props
new file mode 100644
index 0000000..92549ca
--- /dev/null
+++ b/Apewer/_Common.props
@@ -0,0 +1,28 @@
+
+
+
+
+ true
+ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
+ latest
+ CS0108,CS0162,CS0414,CS0612,CS0618,CS0649,CS1589,CS1570,CS1572,CS1573,CS3019,CS3021
+
+
+
+
+ Apewer Libraries
+ 6.3.4
+
+
+
+
+ true
+ true
+
+
+
+
+ DEBUG;TRACE;$(DefineConstants);$(AdditionalConstants)
+
+
+
\ No newline at end of file
diff --git a/Apewer/_Extensions.cs b/Apewer/_Extensions.cs
index d98b9e7..80ec493 100644
--- a/Apewer/_Extensions.cs
+++ b/Apewer/_Extensions.cs
@@ -549,6 +549,9 @@ public static class Extensions
/// 输出二进制。
public static void Binary(this ApiResponse @this, Stream content, string type = "application/octet-stream") => WebUtility.SetBinary(@this, content, type);
+ /// 输出二进制。
+ public static void Binary(this ApiResponse @this, string path, string type = "application/octet-stream") => WebUtility.SetBinary(@this, path, type);
+
/// 输出文件。
public static void File(this ApiResponse @this, Stream stream, string name, string type = "application/octet-stream") => WebUtility.SetFile(@this, stream, name, type);
diff --git a/Apewer/_ChangeLog.md b/ChangeLog.md
similarity index 95%
rename from Apewer/_ChangeLog.md
rename to ChangeLog.md
index fd15662..8dada48 100644
--- a/Apewer/_ChangeLog.md
+++ b/ChangeLog.md
@@ -1,6 +1,16 @@
### 最新提交
+### 6.3.4
+- Logger:支持指定文件路径;
+- Models:StringPairs 支持通过构造函数设置初始容量;
+- Source:去除 Attribute 的 Lock 方法,不再锁定属性;
+- Source:增加 ORM 接口,统一方法;
+- Web:增加 HTTP 头,应对 Edge 浏览器的检查;
+- Web:新增 Response.Binary 重载,支持指定文件路径;
+- Web:ApiOptions 支持设置 X-Content-Type-Options;
+- Web:Staitc 控制器对部分纯文本文件的 Content-Type 加入“utf-8”。
+
### 6.3.3
- 全局:恢复 .NET Standard 2.0 的输出,用于支持 .NET Framework 4.6.1;
- StorageUtility:优化了 ReadFile 的内存占用;