Browse Source

Apewer-6.3.4

dev
王厅 4 years ago
parent
commit
fdb133c201
  1. 28
      Apewer/Apewer.csproj
  2. 4
      Apewer/Logger.cs
  3. 6
      Apewer/Models/StringPairs.cs
  4. 64
      Apewer/Source/Accessor.cs
  5. 102
      Apewer/Source/ColumnAttribute.cs
  6. 12
      Apewer/Source/Example.cs
  7. 46
      Apewer/Source/IOrm.cs
  8. 142
      Apewer/Source/MySql.cs
  9. 6
      Apewer/Source/Record.cs
  10. 91
      Apewer/Source/SqlServer.cs
  11. 965
      Apewer/Source/Sqlite.cs
  12. 29
      Apewer/Source/TableAttribute.cs
  13. 39
      Apewer/Source/TableStructure.cs
  14. 34
      Apewer/Web/ApiMime.cs
  15. 18
      Apewer/Web/ApiOptions.cs
  16. 20
      Apewer/Web/ApiProcessor.cs
  17. 2
      Apewer/Web/ApiResponse.cs
  18. 45
      Apewer/Web/StaticController.cs
  19. 13
      Apewer/Web/WebUtility.cs
  20. 28
      Apewer/_Common.props
  21. 3
      Apewer/_Extensions.cs
  22. 10
      ChangeLog.md

28
Apewer/Apewer.csproj

@ -2,41 +2,21 @@
<!--<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" />-->
<!-- 生成 -->
<Import Project="_Common.props" />
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<LangVersion>latest</LangVersion>
<NoWarn>CS0108,CS0162,CS0414,CS0612,CS0618,CS0649,CS1589,CS1570,CS1572,CS1573,CS3019,CS3021</NoWarn>
<OutputType>Library</OutputType>
<TargetFrameworks>netcoreapp3.1;netstandard2.1;netstandard2.0;net461;net40;net20</TargetFrameworks>
</PropertyGroup>
<!-- 程序集信息 -->
<PropertyGroup>
<Authors>Elivo</Authors>
<Company>Apewer Lab</Company>
<Copyright>Copyright Apewer Lab. All rights reserved.</Copyright>
<Description></Description>
<RootNamespace>Apewer</RootNamespace>
<Product>Apewer Libraries</Product>
<Version>6.3.3</Version>
</PropertyGroup>
<!-- NuGet -->
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<AssemblyName>Apewer</AssemblyName>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IsPackable>true</IsPackable>
<Description></Description>
<PackageId>Apewer</PackageId>
<RootNamespace>Apewer</RootNamespace>
<Title>Apewer</Title>
</PropertyGroup>
<!-- Debug -->
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>DEBUG;TRACE;$(DefineConstants);$(AdditionalConstants)</DefineConstants>
</PropertyGroup>
<!-- .NET Standard 2.1 -->
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.1'">
<AssemblyTitle>$(AssemblyName) - .NET Standard 2.1</AssemblyTitle>

4
Apewer/Logger.cs

@ -167,7 +167,7 @@ namespace Apewer
internal static object ConsoleLocker = new object();
/// <summary>获取用于保存日志文件的路径。</summary>
public static Func<string> FilePathGetter { get; set; }
public static Func<Logger, string> 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;

6
Apewer/Models/StringPairs.cs

@ -12,6 +12,12 @@ namespace Apewer.Models
public class StringPairs : List<KeyValuePair<string, string>>, IToJson
{
/// <summary></summary>
public StringPairs() : base() { }
/// <summary></summary>
public StringPairs(int capacity) : base(capacity) { }
/// <summary>添加项。返回错误信息。</summary>
public string Add(string key, string value)
{

64
Apewer/Source/Accessor.cs

@ -1,64 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Source
{
/// <summary></summary>
public abstract class Accessor<T> where T : class, IDatabase, new()
{
private T _source = null;
private bool _disposed = false;
/// <summary></summary>
public Accessor() { }
/// <summary></summary>
protected virtual T Source
{
get
{
if (_source == null) _source = new T();
return _source;
}
}
/// <summary></summary>
public virtual bool Connected
{
get
{
return (_source == null) ? false : _source.Online;
}
}
/// <summary></summary>
public virtual bool Disposed
{
get { return _disposed; }
}
/// <summary></summary>
public virtual void Close()
{
if (_source == null) return;
_source.Close();
}
/// <summary></summary>
public virtual void Dispose()
{
if (_source != null)
{
_source.Dispose();
_source = null;
}
_disposed = true;
}
}
}

102
Apewer/Source/ColumnAttribute.cs

@ -21,105 +21,71 @@ namespace Apewer.Source
private bool _independent = false;
private bool _locked = false;
/// <summary>使用自动的列名称。当类型为 VarChar 或 NVarChar 时必须指定长度。</summary>
/// <exception cref="System.ArgumentException"></exception>
public ColumnAttribute(ColumnType type = ColumnType.NVarChar, int length = 191)
private void Init(string field, ColumnType type, int length, bool underline)
{
_field = string.IsNullOrEmpty(field) ? "" : TableStructure.RestrictName(field, underline);
_type = type;
switch (type)
{
New(null, type, length, true);
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;
}
}
/// <summary>使用自动的列名称。当类型为 VarChar 或 NVarChar 时必须指定长度。</summary>
/// <exception cref="System.ArgumentException"></exception>
public ColumnAttribute(ColumnType type = ColumnType.NVarChar, int length = 191) => Init(null, type, length, true);
/// <summary>使用指定的列名称。当类型为 VarChar 或 NVarChar 时必须指定长度。</summary>
/// <exception cref="System.ArgumentException"></exception>
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);
/// <summary>属性。</summary>
public PropertyInfo Property
{
get { return _property; }
internal set
{
if (_locked) return;
_property = value;
}
get => _property;
internal set => _property = value;
}
/// <summary>字段名。</summary>
public string Field
{
get { return _field; }
set
{
if (_locked) return;
_field = value;
}
get => _field;
set => _field = value;
}
/// <summary>指定字段的最大长度。</summary>
public int Length
{
get { return _length; }
set
{
if (_locked) return;
_length = value;
}
get => _length;
set => _length = value;
}
/// <summary>字段类型。</summary>
public ColumnType Type
{
get { return _type; }
set
{
if (_locked) return;
_type = value;
}
get => _type;
set => _type = value;
}
/// <summary>Independent 特性。</summary>
public bool Independent
{
get { return _independent; }
internal set { _independent = value; }
}
/// <exception cref="System.ArgumentException"></exception>
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();
}
/// <summary>锁定属性,阻止修改。</summary>
public void Lock()
{
_locked = true;
get => _independent;
internal set => _independent = value;
}
}

12
Apewer/Source/Example.cs

@ -24,22 +24,22 @@ namespace Apewer.Source
}
/// <summary></summary>
public static IExecute InvalidExecuteConnection { get { return CreateExecuteError("连接无效。"); } }
public static IExecute InvalidExecuteConnection => CreateExecuteError("连接无效。");
/// <summary></summary>
public static IExecute InvalidExecuteStatement { get { return CreateExecuteError("语句无效。"); } }
public static IExecute InvalidExecuteStatement => CreateExecuteError("语句无效。");
/// <summary></summary>
public static IExecute InvalidExecuteParameters { get { return CreateExecuteError("参数无效。"); } }
public static IExecute InvalidExecuteParameters => CreateExecuteError("参数无效。");
/// <summary></summary>
public static IQuery InvalidQueryConnection { get { return CreateQueryError("连接无效。"); } }
public static IQuery InvalidQueryConnection => CreateQueryError("连接无效。");
/// <summary></summary>
public static IQuery InvalidQueryStatement { get { return CreateQueryError("语句无效。"); } }
public static IQuery InvalidQueryStatement => CreateQueryError("语句无效。");
/// <summary></summary>
public static IQuery InvalidQueryParameters { get { return CreateQueryError("参数无效。"); } }
public static IQuery InvalidQueryParameters => CreateQueryError("参数无效。");
}

46
Apewer/Source/IOrm.cs

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Source
{
/// <summary>数据库引擎支持 ORM 访问。</summary>
public interface IOrm : IDatabase
{
/// <summary>初始化指定类型,以创建表或增加字段。</summary>
/// <param name="model">要初始化的类型。</param>
/// <returns>错误信息。当成功时候返回空字符串。</returns>
public string Initialize(Type model);
/// <summary>插入记录。</summary>
/// <param name="record">要插入的记录实体。</param>
/// <returns>错误信息。当成功时候返回空字符串。</returns>
public string Insert(Record record);
/// <summary>更新记录。</summary>
/// <param name="record">要插入的记录实体。</param>
/// <returns>错误信息。当成功时候返回空字符串。</returns>
public string Update(Record record);
/// <summary>获取指定类型的主键,按 Flag 属性筛选。</summary>
/// <param name="model">要查询的类型。</param>
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
public Result<List<string>> Keys(Type model, long flag = 0);
/// <summary>获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。</summary>
/// <param name="key">目标记录的主键。</param>
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
public Result<T> Get<T>(string key, long flag = 0) where T : Record;
/// <summary>使用指定语句查询,获取查询结果。</summary>
public Result<List<T>> Query<T>(string sql) where T : Record;
/// <summary>查询所有记录。</summary>
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
public Result<List<T>> Query<T>(long flag = 0) where T : Record;
}
}

142
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
{
/// <summary></summary>
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 日志。
/// <summary>获取或设置日志记录。</summary>
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
/// <summary></summary>
public IExecute Execute(string sql) => Execute(sql, null as IEnumerable<IDataParameter>);
#endregion
#endregion
#region linq
#region ORM
private List<string> QueryFirstColumn(string sql)
private List<string> FirstColumn(string sql)
{
using (var query = Query(sql) as Query) return query.ReadColumn();
}
/// <summary></summary>
public List<string> QueryAllTableNames()
public List<string> 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);
}
/// <summary></summary>
public List<string> QueryAllViewNames()
public List<string> 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);
}
/// <summary></summary>
public List<string> QueryAllColumnNames(string table)
public List<string> ColumnNames(string table)
{
var sql = TextUtility.Merge("select column_name from information_schema.columns where table_schema='", _store, "' and table_name='", TextUtility.AntiInject(table), "';");
return QueryFirstColumn(sql);
return FirstColumn(sql);
}
/// <summary>获取用于创建表的语句。</summary>
@ -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<string>(columns.Count);
@ -366,7 +373,7 @@ namespace Apewer.Source
}
/// <summary></summary>
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
}
/// <summary></summary>
public string CreateTable(Type model)
{
return CreateTable(model, out string sql);
}
public string Initialize(Type model) => Initialize(model, out string sql);
/// <summary></summary>
public string CreateTable<T>() where T : Record
{
return CreateTable(typeof(T));
}
public string Initialize<T>() where T : Record => Initialize(typeof(T));
/// <summary></summary>
public string CreateTable(Record model)
{
if (model == null) return "参数无效。";
return CreateTable(model.GetType());
}
public string Initialize(Record model) => (model == null) ? "参数无效。" : Initialize(model.GetType());
/// <summary>插入记录。成功时候返回空字符串,发生异常时返回异常信息。</summary>
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
/// <para>更新记录,实体中的 Created 和 Key 属性不被更新。成功时返回空字符串,发生异常时返回异常信息。</para>
/// <para>无法更新拥有 Independent 特性的模型。</para>
/// </summary>
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;
}
/// <summary></summary>
public Result<List<T>> QueryRecords<T>(string sql) where T : Record
public Result<List<T>> Query<T>(string sql) where T : Record
{
if (sql.IsEmpty()) return new Result<List<T>>(new ArgumentException());
try
@ -480,7 +475,7 @@ namespace Apewer.Source
}
/// <summary>获取记录。</summary>
public Result<T> QueryRecord<T>(string key, long flag = 0) where T : Record
public Result<T> Get<T>(string key, long flag = 0) where T : Record
{
var k = TextUtility.SafeKey(key);
if (k.IsEmpty()) new Result<T>(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<T>(sql);
var result = Query<T>(sql);
var list = result.Entity;
if (list != null && list.Count > 0) return new Result<T>(list[0]);
return new Result<T>(null, "无结果。");
@ -502,14 +497,14 @@ namespace Apewer.Source
}
/// <summary>获取所有记录。Flag 为 0 时将忽略 Flag 条件。</summary>
public Result<List<T>> QueryRecords<T>(long flag = 0) where T : Record
public Result<List<T>> Query<T>(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<T>(sql);
return Query<T>(sql);
}
catch (Exception exception)
{
@ -520,7 +515,7 @@ namespace Apewer.Source
/// <summary>获取记录。</summary>
/// <param name="skip">要跳过的记录数,可用最小值为 0。</param>
/// <param name="count">要获取的记录数,可用最小值为 1。</param>
public Result<List<T>> QueryRecords<T>(int skip, int count) where T : Record
public Result<List<T>> Query<T>(int skip, int count) where T : Record
{
try
{
@ -528,7 +523,7 @@ namespace Apewer.Source
if (count < 1) return new Result<List<T>>(new ArgumentOutOfRangeException(nameof(count)));
var tableName = TableStructure.ParseTable(typeof(T)).Name;
var sql = $"select * from `{tableName}` where _flag = 1 limit {skip}, {count}; ";
return QueryRecords<T>(sql);
return Query<T>(sql);
}
catch (Exception exception)
{
@ -536,17 +531,17 @@ namespace Apewer.Source
}
}
/// <summary>查询有效的 Key 值。</summary>
public Result<List<string>> QueryKeys(Type model, long flag = 0)
/// <summary>>获取指定类型的主键,按 Flag 属性筛选。</summary>
public Result<List<string>> 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<List<string>>(new Exception("参数无效。"));
}
/// <summary>查询有效的 Key 值。</summary>
public Result<List<string>> QueryKeys<T>(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<string>>(list);
}
}
catch (Exception ex)
{
return new Result<List<string>>(ex);
}
}
/// <summary>>获取指定类型的主键,按 Flag 属性筛选。</summary>
public Result<List<string>> Keys<T>(long flag = 0) where T : Record => Keys(typeof(T), flag);
/// <summary>对表添加列,返回错误信息。</summary>
/// <typeparam name="T">记录类型。</typeparam>
@ -635,9 +611,9 @@ namespace Apewer.Source
return error;
}
#endregion
#endregion
#region static
#region static
/// <summary>对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。</summary>
public static string Escape(string text, int bytes = 0)
@ -996,7 +972,7 @@ namespace Apewer.Source
return result;
}
#endregion
#endregion
}

6
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;
}
/// <summary>枚举带有 Table 特性的 <typeparamref name="T"/> 派生类型。</summary>

91
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
/// <summary>用于快速连接 Microsoft SQL Server 数据库的辅助。</summary>
[Serializable]
public class SqlServer : IDatabase, IDisposable
public class SqlServer : IDatabase, IOrm, IDisposable
{
#region 变量定义。
@ -322,7 +322,7 @@ namespace Apewer.Source
}
/// <summary>查询数据库中的所有表名。</summary>
public List<string> QueryAllTableNames()
public List<string> TableNames()
{
var list = new List<string>();
if (Connect())
@ -341,7 +341,7 @@ namespace Apewer.Source
}
/// <summary>查询数据库实例中的所有数据库名。</summary>
public List<string> QueryAllStoreNames()
public List<string> StoreNames()
{
var list = new List<string>();
if (Connect())
@ -364,7 +364,7 @@ namespace Apewer.Source
}
/// <summary>查询表中的所有列名。</summary>
public List<string> QueryAllColumnNames(string tableName)
public List<string> ColumnNames(string tableName)
{
var list = new List<string>();
if (Connect())
@ -384,16 +384,10 @@ namespace Apewer.Source
}
/// <summary>创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。</summary>
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);
/// <summary>创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。</summary>
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<string>();
@ -478,18 +472,18 @@ namespace Apewer.Source
}
/// <summary>插入记录。成功时候返回空字符串,发生异常时返回异常信息。</summary>
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
/// <para>更新记录,实体中的 Created 和 Key 属性不被更新。成功时返回空字符串,发生异常时返回异常信息。</para>
/// <para>无法更新拥有 Independent 特性的模型。</para>
/// </summary>
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
}
/// <summary>获取具有指定 Key 的记录。</summary>
public Result<T> QueryRecord<T>(string key, long flag = 0) where T : Record
public Result<T> Get<T>(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
}
/// <summary>获取记录。</summary>
public Result<List<T>> QueryRecords<T>(long flag = 0) where T : Record
public Result<List<T>> Query<T>(long flag = 0) where T : Record
{
try
{
@ -588,7 +582,7 @@ namespace Apewer.Source
}
/// <summary>查询有效的 Key 值。</summary>
public Result<List<string>> QueryKeys(Type model, long flag = 0)
public Result<List<string>> Keys(Type model, long flag = 0)
{
if (model != null)
{
@ -616,29 +610,7 @@ namespace Apewer.Source
}
/// <summary>查询有效的 Key 值。</summary>
public Result<List<string>> QueryKeys<T>(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<string>();
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<string>>(list);
}
catch (Exception ex)
{
return new Result<List<string>>(ex);
}
}
public Result<List<string>> Keys<T>(long flag = 0) where T : Record => Keys(typeof(T), flag);
#endregion
@ -826,10 +798,7 @@ namespace Apewer.Source
}
/// <summary>指定的连接凭据是否符合连接要求,默认指定 master 数据库。</summary>
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);
/// <summary>指定的连接凭据是否符合连接要求。</summary>
public static bool Proven(string address, string store, string user, string pass)
@ -934,7 +903,7 @@ namespace Apewer.Source
/// <summary>生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。</summary>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
public static string GenerateInsertStatement(string table, IEnumerable<IDataParameter> parameters)
private static string GenerateInsertStatement(string table, IEnumerable<IDataParameter> parameters)
{
if (table == null) throw new ArgumentNullException(nameof(table));
var tableName = TextUtility.AntiInject(table, 255);
@ -949,7 +918,7 @@ namespace Apewer.Source
/// <summary>生成 UPDATE 语句,键字段名为“_key”。表名必须有效,键值必须有效,无有效参数时将获取空结果。</summary>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
public static string GenerateUpdateStatement(string table, string key, IEnumerable<IDataParameter> parameters)
private static string GenerateUpdateStatement(string table, string key, IEnumerable<IDataParameter> parameters)
{
if (table == null) throw new ArgumentNullException(nameof(table));
var t = TextUtility.AntiInject(table, 255);

965
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
{
/// <summary>用于快速连接 SQLite 数据库的辅助。</summary>
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;
}
/// <summary>连接内存。</summary>
public Sqlite() => VarInit(Memory, new Timeout(), null, null);
/// <summary>连接指定文件。</summary>
public Sqlite(string path) => VarInit(path, new Timeout(), null, null);
/// <summary>连接指定文件。</summary>
private Sqlite(string path, byte[] passData) => VarInit(path, new Timeout(), null, passData);
/// <summary>连接指定文件。</summary>
public Sqlite(string path, string pass) => VarInit(path, new Timeout(), pass, null);
/// <summary>连接指定文件。</summary>
public Sqlite(string path, Timeout timeout) => VarInit(path, timeout, null, null);
/// <summary>连接指定文件。</summary>
private Sqlite(string path, Timeout timeout, byte[] pass) => VarInit(path, timeout, null, pass);
/// <summary>连接指定文件。</summary>
public Sqlite(string path, Timeout timeout, string pass) => VarInit(path, timeout, pass, null);
#endregion
#region 日志。
/// <summary>获取或设置日志记录。</summary>
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 实现接口。
/// <summary>数据库是否已经连接。</summary>
public bool Online { get => _db != null && _db.State == ConnectionState.Open; }
/// <summary>连接数据库,若未连接则尝试连接。</summary>
/// <returns>是否已连接。</returns>
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;
}
}
/// <summary>关闭连接,并释放对象所占用的系统资源。</summary>
public void Close()
{
if (_db != null)
{
//_db.Close();
_db.Dispose();
_db = null;
}
}
/// <summary>关闭连接,释放对象所占用的系统资源,并清除连接信息。</summary>
public void Dispose() { Close(); }
/// <summary>查询。</summary>
public IQuery Query(string sql) => Query(sql, null);
/// <summary>查询。</summary>
public IQuery Query(string sql, IEnumerable<IDataParameter> 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;
}
/// <summary>执行单条 Transact-SQL 语句。</summary>
public IExecute Execute(string sql) => Execute(sql, null);
/// <summary>执行单条 Transact-SQL 语句,并加入参数。</summary>
public IExecute Execute(string sql, IEnumerable<IDataParameter> 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 属性。
/// <summary>获取当前的 SQLiteConnection 对象。</summary>
public IDbConnection Connection { get => _db; }
/// <summary>获取或设置超时。</summary>
public Timeout Timeout { get => _timeout; set => _timeout = value; }
/// <summary>获取或设置连接字符串,连接字符串非空时将忽略 Path 属性。数据库在线时无法设置。</summary>
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;
}
}
/// <summary>获取或设置数据库路径(文件或内存)。数据库在线时无法设置。</summary>
public string Path
{
get { return _path; }
set
{
if (Online) return;
_path = string.IsNullOrEmpty(value) ? "" : value;
}
}
/// <summary>获取或设置数据库密码。数据库在线时无法设置。</summary>
public string Password
{
get { return _pass; }
set
{
if (Online) return;
_pass = string.IsNullOrEmpty(value) ? "" : value;
}
}
/// <summary>获取或设置数据库密码。数据库在线时无法设置。</summary>
private byte[] PasswordData
{
get { return _passdata; }
set
{
if (Online) return;
_passdata = (value == null) ? BinaryUtility.EmptyBytes : value;
}
}
/// <summary>保存当前数据库到文件,若文件已存在则将重写文件。</summary>
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;
}
/// <summary>保存当前数据库到文件,若文件已存在则将重写文件。</summary>
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;
}
/// <summary>保存当前数据库到目标数据库。</summary>
public bool Save(Sqlite destination)
{
var error = Backup(this, destination);
return string.IsNullOrEmpty(error);
}
/// <summary>加载文件到当前数据库。</summary>
public bool Load(string path)
{
var temp = new Sqlite(path);
var result = Load(temp);
temp.Close();
return result;
}
/// <summary>加载文件到当前数据库。</summary>
public bool Load(string path, params byte[] pass)
{
var temp = new Sqlite(path, pass);
var result = Load(temp);
temp.Close();
return result;
}
/// <summary>加载文件到当前数据库。</summary>
public bool Load(string path, string pass)
{
var temp = new Sqlite(path, pass);
var result = Load(temp);
temp.Close();
return result;
}
/// <summary>加载源数据库到当前数据库。</summary>
public bool Load(Sqlite source)
{
var error = Backup(source, this);
return string.IsNullOrEmpty(error);
}
#endregion
#region ORM。
/// <summary>查询数据库中的所有表名。</summary>
public List<string> TableNames()
{
var list = new List<string>();
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;
}
/// <summary>查询数据库中的所有视图名。</summary>
public List<string> ViewNames()
{
var list = new List<string>();
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;
}
/// <summary>查询表中的所有列名。</summary>
public List<string> ColumnNames(string table)
{
var list = new List<string>();
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;
}
/// <summary>创建表,不修改已存在表。成功时返回空字符串,发生异常时返回异常信息。</summary>
public string Initialize(Record model) => model == null ? "参数无效。" : Initialize(model.GetType());
/// <summary>创建表,不修改已存在表。成功时返回空字符串,发生异常时返回异常信息。</summary>
public string Initialize<T>() where T : Record => Initialize(typeof(T));
/// <summary>创建表,不修改已存在表。当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。</summary>
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<string>();
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;
}
}
/// <summary>插入记录。成功时候返回空字符串,发生异常时返回异常信息。</summary>
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<IDataParameter>)parameters);
var execute = Execute(sql, parameters);
if (execute.Success && execute.Rows > 0) return TextUtility.EmptyString;
return execute.Error;
}
/// <summary>更新记录,实体中的 Created 和 Key 属性不被更新。成功时返回空字符串,发生异常时返回异常信息。</summary>
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;
}
/// <summary>获取具有指定 Key 的记录。</summary>
public Result<T> Get<T>(string key) where T : Record => Get<T>(key, 0);
/// <summary>获取具有指定 Key 的记录。</summary>
public Result<T> Get<T>(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<T>();
query.Dispose();
if (list.Count > 0) return new Result<T>(list[0]);
}
catch (Exception ex)
{
return new Result<T>(ex);
}
}
return new Result<T>(new Exception("参数无效。"));
}
/// <summary>查询多条记录。</summary>
public Result<List<T>> Query<T>() where T : Record => Query<T>(0);
/// <summary>查询多条记录。</summary>
public Result<List<T>> Query<T>(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<T>();
query.Dispose();
return new Result<List<T>>(list);
}
catch (Exception ex)
{
return new Result<List<T>>(ex);
}
}
/// <summary>获取按指定语句查询到的所有记录。</summary>
public Result<List<T>> Query<T>(string sql) where T : Record
{
using (var query = Query(sql) as Query)
{
if (query.Exception == null) return new Result<List<T>>(query.Fill<T>());
else return new Result<List<T>>(query.Exception);
}
}
/// <summary>查询所有有效的 Key 值。</summary>
public Result<List<string>> Keys<T>() where T : Record => Keys<T>(0);
/// <summary>查询所有有效的 Key 值。</summary>
public Result<List<string>> Keys<T>(long flag) where T : Record => Keys(typeof(T), flag);
/// <summary>查询所有有效的 Key 值。</summary>
public Result<List<string>> Keys(Type model) => Keys(model, 0);
/// <summary>查询所有有效的 Key 值。</summary>
public Result<List<string>> Keys(Type model, long flag)
{
if (model != null)
{
try
{
var list = new List<string>();
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<string>>(list);
}
catch (Exception ex)
{
return new Result<List<string>>(ex);
}
}
return new Result<List<string>>(new Exception("参数无效。"));
}
#endregion
#region static
/// <summary>对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。</summary>
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);
}
/// <summary>创建参数。</summary>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public static SQLiteParameter CreateDataParameter(Parameter parameter)
{
if (parameter == null) throw new InvalidOperationException("参数无效。");
return CreateDataParameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value);
}
/// <summary>创建参数。</summary>
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;
}
/// <summary>创建参数。</summary>
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;
}
/// <summary>创建参数。</summary>
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;
}
/// <summary>备份数据库,返回错误信息。</summary>
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;
}
}
/// <summary>创建参数。</summary>
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;
}
/// <summary>创建参数。</summary>
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;
}
/// <summary>整理数据库,压缩未使用的空间。</summary>
public const string Vacuum = "vacuum";
/// <summary>内存数据库的地址。</summary>
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<string> GetParametersNames(IEnumerable<IDataParameter> parameters)
{
var columns = new List<string>();
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<string> columns)
{
var r = TextUtility.EmptyString;
var t = TextUtility.AntiInject(table, 255);
if (columns != null && !TextUtility.IsBlank(t))
{
var count = 0;
var names = new List<string>();
var values = new List<string>();
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;
}
/// <summary>生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。</summary>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
public static string GenerateInsertStatement(string table, IEnumerable<IDataParameter> 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<string> 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<string>();
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;
}
/// <summary>生成 UPDATE 语句,键字段名为“_key”。表名必须有效,键值必须有效,无有效参数时将获取空结果。</summary>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
public static string GenerateUpdateStatement(string table, string key, IEnumerable<IDataParameter> 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

29
Apewer/Source/TableAttribute.cs

@ -15,8 +15,6 @@ namespace Apewer.Source
private string _name;
private bool _independent = false;
private bool _locked = false;
/// <summary></summary>
public TableAttribute(string name = null)
{
@ -32,36 +30,19 @@ namespace Apewer.Source
/// <summary>表名。</summary>
public string Name
{
get { return _name; }
set
{
if (_locked) return;
_name = TableStructure.RestrictName(value, false);
}
get => _name;
set => _name = TableStructure.RestrictName(value, false);
}
/// <summary></summary>
public bool Independent
{
get { return _independent; }
internal set
{
if (_locked) return;
_independent = value;
}
get => _independent;
internal set => _independent = value;
}
/// <summary></summary>
public override int GetHashCode()
{
return _name.GetHashCode();
}
/// <summary>锁定属性,阻止修改。</summary>
public void Lock()
{
_locked = true;
}
public override int GetHashCode() => _name.GetHashCode();
}

39
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
/// <summary>不依赖 Record 公共属性。</summary>
public bool Independent
{
get { return _independent; }
private set { _independent = value; }
get => _independent;
private set => _independent = value;
}
/// <summary>表名称。</summary>
public string Table
{
get { return _tablename; }
private set { _tablename = value ?? ""; }
get => _tablename;
private set => _tablename = value ?? "";
}
/// <summary>列信息。</summary>
public Dictionary<string, ColumnAttribute> Columns
{
get
{
if (_locked)
{
var copy = new Dictionary<string, ColumnAttribute>(_columns.Count);
foreach (var c in _columns) copy.Add(c.Key, c.Value);
return copy;
}
return _columns;
}
private set { _columns = value; }
}
/// <summary>锁定属性,阻止修改。</summary>
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<IndependentAttribute>(type, true);
// 锁定属性。
ta.Lock();
// 加入缓存。
if (useCache)
{
@ -291,9 +267,6 @@ namespace Apewer.Source
ca.Property = property;
// 锁定属性。
ca.Lock();
return ca;
}

34
Apewer/Web/ApiMime.cs

@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[Serializable]
public sealed class ApiMime
{
/// <summary></summary>
public string Extension { get; set; }
/// <summary></summary>
public string ContentType { get; set; }
/// <summary>缓存过期时间,单位为秒,默认值 0 为不缓存(立即过期)。</summary>
public int Expires { get; set; }
/// <summary></summary>
public ApiMime() { }
internal ApiMime(string ext, string type, int expires)
{
Extension = ext;
ContentType = type;
Expires = expires;
}
}
}

18
Apewer/Web/ApiOptions.cs

@ -9,31 +9,35 @@ namespace Apewer.Web
public static class ApiOptions
{
/// <summary>允许 Invoker 解析 favicon.ico 请求。</summary>
/// <summary>允许解析 favicon.ico 请求。</summary>
/// <remarks>默认值:不允许,响应空。</remarks>
public static bool AllowFavIcon { get; set; } = false;
/// <summary>允许 Invoker 解析 robots.txt 请求。</summary>
/// <summary>允许解析 robots.txt 请求。</summary>
/// <remarks>默认值:不允许,拒绝搜索引擎收录根目录。</remarks>
public static bool AllowRobots { get; set; } = false;
/// <summary>允许 Invoker 枚举输出 Applications 或 Functions。</summary>
/// <summary>允许响应标头中包含 X-Content-Type-Options: nosiff。</summary>
/// <remarks>默认值:不包含。当设置默认控制器时自动启用此属性。</remarks>
public static bool AllowContentTypeOptions { get; set; } = false;
/// <summary>允许枚举输出 Applications 或 Functions。</summary>
/// <remarks>默认值:不允许,不输出列表。</remarks>
public static bool AllowEnumerate { get; set; } = false;
/// <summary>允许 Invoker 输出 Exception 对象的属性。</summary>
/// <summary>允许输出 Exception 对象的属性。</summary>
/// <remarks>默认值:不允许输出。</remarks>
public static bool AllowException { get; set; } = false;
/// <summary>允许 Invoker 输出的 Json 对象缩进。</summary>
/// <summary>允许输出的 Json 对象缩进。</summary>
/// <remarks>默认值:不缩进。</remarks>
public static bool JsonIndent { get; set; } = false;
/// <summary>允许 Invoker 输出 Application 列表时包含模块名称。</summary>
/// <summary>允许输出 Application 列表时包含模块名称。</summary>
/// <remarks>默认值:不包含。</remarks>
public static bool WithModuleName { get; set; } = false;
/// <summary>允许 Invoker 输出 Application 列表时包含类型名称。</summary>
/// <summary>允许输出 Application 列表时包含类型名称。</summary>
/// <remarks>默认值:不包含。</remarks>
public static bool WithTypeName { get; set; } = false;

20
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;
}

2
Apewer/Web/ApiResponse.cs

@ -68,6 +68,8 @@ namespace Apewer.Web
internal byte[] BinaryBytes;
internal string BinaryPath;
internal string BinaryType;
#endregion

45
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";

13
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";
}
/// <summary>输出二进制。</summary>
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";
}

28
Apewer/_Common.props

@ -0,0 +1,28 @@
<Project>
<!-- 生成 -->
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<LangVersion>latest</LangVersion>
<NoWarn>CS0108,CS0162,CS0414,CS0612,CS0618,CS0649,CS1589,CS1570,CS1572,CS1573,CS3019,CS3021</NoWarn>
</PropertyGroup>
<!-- 程序集信息 -->
<PropertyGroup>
<Product>Apewer Libraries</Product>
<Version>6.3.4</Version>
</PropertyGroup>
<!-- NuGet -->
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IsPackable>true</IsPackable>
</PropertyGroup>
<!-- Debug -->
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>DEBUG;TRACE;$(DefineConstants);$(AdditionalConstants)</DefineConstants>
</PropertyGroup>
</Project>

3
Apewer/_Extensions.cs

@ -549,6 +549,9 @@ public static class Extensions
/// <summary>输出二进制。</summary>
public static void Binary(this ApiResponse @this, Stream content, string type = "application/octet-stream") => WebUtility.SetBinary(@this, content, type);
/// <summary>输出二进制。</summary>
public static void Binary(this ApiResponse @this, string path, string type = "application/octet-stream") => WebUtility.SetBinary(@this, path, type);
/// <summary>输出文件。</summary>
public static void File(this ApiResponse @this, Stream stream, string name, string type = "application/octet-stream") => WebUtility.SetFile(@this, stream, name, type);

10
Apewer/_ChangeLog.md → 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 的内存占用;
Loading…
Cancel
Save