35 changed files with 1680 additions and 753 deletions
@ -0,0 +1,480 @@ |
|||
#if DEBUG
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Data; |
|||
using System.Data.Common; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.Source |
|||
{ |
|||
|
|||
/// <summary>数据库客户端基类。</summary>
|
|||
|
|||
|
|||
public abstract class DbClient : IDbClient |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public virtual Logger Logger { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public DbClient(Timeout timeout) { _timeout = timeout ?? Timeout.Default; } |
|||
|
|||
#region connection
|
|||
|
|||
Timeout _timeout = null; |
|||
IDbConnection _conn = null; |
|||
string _str = null; |
|||
|
|||
/// <summary></summary>
|
|||
public Timeout Timeout { get => _timeout; set => _timeout = value ?? Timeout.Default; } |
|||
|
|||
/// <summary>获取当前的 SqlConnection 对象。</summary>
|
|||
public IDbConnection Connection { get => _conn; } |
|||
|
|||
/// <summary></summary>
|
|||
public bool Online { get => _conn == null ? false : (_conn.State == ConnectionState.Open); } |
|||
|
|||
/// <summary>连接字符串。</summary>
|
|||
public string ConnectionString { get => _str; } |
|||
|
|||
/// <summary>连接数据库,若未连接则尝试连接,获取连接成功的状态。</summary>
|
|||
public string Connect() |
|||
{ |
|||
if (_conn == null) |
|||
{ |
|||
_str = NewConnectionString(); |
|||
_conn = NewConnection(); |
|||
_conn.ConnectionString = _str; |
|||
} |
|||
else |
|||
{ |
|||
if (_conn.State == ConnectionState.Open) return null; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
_conn.Open(); |
|||
if (_conn.State == ConnectionState.Open) return null; |
|||
var message = $"连接后状态为 {_conn.State},无法验证打开状态。"; |
|||
Logger.Error(this, "Connect", message, _str); |
|||
return "连接失败," + message; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Logger.Error(this, "Connect", ex.GetType().Name, ex.Message, _str); |
|||
Close(); |
|||
return ex.Message; |
|||
} |
|||
} |
|||
|
|||
/// <summary>关闭连接,并释放对象所占用的系统资源。</summary>
|
|||
public void Close() |
|||
{ |
|||
if (_conn != null) |
|||
{ |
|||
if (_transaction != null) |
|||
{ |
|||
if (_autocommit) Commit(); |
|||
else Rollback(); |
|||
} |
|||
_conn.Close(); |
|||
_conn.Dispose(); |
|||
_conn = null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>关闭连接,释放对象所占用的系统资源,并清除连接信息。</summary>
|
|||
public void Dispose() { Close(); } |
|||
|
|||
#endregion
|
|||
|
|||
#region transaction
|
|||
|
|||
private IDbTransaction _transaction = null; |
|||
private bool _autocommit = false; |
|||
|
|||
/// <summary>
|
|||
/// <para>启动事务,可指定事务锁定行为。</para>
|
|||
/// <para>Chaos<br />无法覆盖隔离级别更高的事务中的挂起的更改。</para>
|
|||
/// <para>ReadCommitted<br />在正在读取数据时保持共享锁,以避免脏读,但是在事务结束之前可以更改数据,从而导致不可重复的读取或幻像数据。</para>
|
|||
/// <para>ReadUncommitted<br />可以进行脏读,意思是说,不发布共享锁,也不接受独占锁。</para>
|
|||
/// <para>RepeatableRead<br />在查询中使用的所有数据上放置锁,以防止其他用户更新这些数据。 防止不可重复的读取,但是仍可以有幻像行。</para>
|
|||
/// <para>Serializable<br />在 System.Data.DataSet 上放置范围锁,以防止在事务完成之前由其他用户更新行或向数据集中插入行。</para>
|
|||
/// <para>Snapshot<br />通过在一个应用程序正在修改数据时存储另一个应用程序可以读取的相同数据版本来减少阻止。 表示您无法从一个事务中看到在其他事务中进行的更改,即便重新查询也是如此。</para>
|
|||
/// <para>Unspecified = -1<br />正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。</para>
|
|||
/// </summary>
|
|||
/// <param name="commit">在连接的生命周期结束时未结束事务,指定 TRUE 将自动提交,指定 FALSE 将自动回滚。</param>
|
|||
/// <param name="isolation">指定事务锁定行为,不指定时将使用默认值。</param>
|
|||
public string Begin(bool commit = false, Class<IsolationLevel> isolation = null) |
|||
{ |
|||
if (_transaction != null) return "已存在未完成的事务,无法再次启动。"; |
|||
var connect = Connect(); |
|||
if (connect.NotEmpty()) return $"无法启动事务:连接失败。(${connect})"; |
|||
try |
|||
{ |
|||
_transaction = isolation ? _conn.BeginTransaction(isolation.Value) : _conn.BeginTransaction(); |
|||
_autocommit = commit; |
|||
return null; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Logger.Error(this, "Begin", ex.Message()); |
|||
return ex.Message(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>提交事务。</summary>
|
|||
public string Commit() |
|||
{ |
|||
if (_transaction == null) return "事务不存在。"; |
|||
try |
|||
{ |
|||
_transaction.Commit(); |
|||
RuntimeUtility.Dispose(_transaction); |
|||
_transaction = null; |
|||
return null; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
RuntimeUtility.Dispose(_transaction); |
|||
_transaction = null; |
|||
Logger.Error(this, "Commit", ex.Message()); |
|||
return ex.Message(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>从挂起状态回滚事务。</summary>
|
|||
public string Rollback() |
|||
{ |
|||
if (_transaction == null) return "事务不存在。"; |
|||
try |
|||
{ |
|||
_transaction.Rollback(); |
|||
RuntimeUtility.Dispose(_transaction); |
|||
_transaction = null; |
|||
return null; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
RuntimeUtility.Dispose(_transaction); |
|||
_transaction = null; |
|||
Logger.Error(this, "Rollback", ex.Message()); |
|||
return ex.Message(); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region ado
|
|||
|
|||
/// <summary>查询。</summary>
|
|||
public IQuery Query(string sql) => Query(sql, null); |
|||
|
|||
/// <summary>查询。</summary>
|
|||
public IQuery Query(string sql, IEnumerable<IDataParameter> parameters) |
|||
{ |
|||
if (TextUtility.IsEmpty(sql)) return new Query(false, "语句无效。"); |
|||
|
|||
var connected = Connect(); |
|||
if (connected.NotEmpty()) return new Query(false, connected); |
|||
|
|||
try |
|||
{ |
|||
using (var command = NewCommand()) |
|||
{ |
|||
command.Connection = _conn; |
|||
if (_timeout != null) command.CommandTimeout = Timeout.Query; |
|||
command.CommandText = sql; |
|||
if (parameters != null) |
|||
{ |
|||
foreach (var parameter in parameters) |
|||
{ |
|||
if (parameter != null) command.Parameters.Add(parameter); |
|||
} |
|||
} |
|||
|
|||
var ex = null as Exception; |
|||
var da = null as IDataAdapter; |
|||
try { da = NewDataAdapter(command); } |
|||
catch (Exception adapterEx) { ex = adapterEx; } |
|||
if (ex == null) |
|||
{ |
|||
using (var ds = new DataSet()) |
|||
{ |
|||
da.Fill(ds); |
|||
if (ds.Tables.Count > 0) |
|||
{ |
|||
var tables = new DataTable[ds.Tables.Count]; |
|||
ds.Tables.CopyTo(tables, 0); |
|||
return new Query(tables, true); |
|||
} |
|||
else |
|||
{ |
|||
Logger.Error(this, "Query", "查询结果不包含任何数据表。", sql); |
|||
return new Query(false, "查询结果不包含任何数据表。"); |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
Logger.Error(this, "Query", ex.GetType().FullName, ex.Message, sql); |
|||
return new Query(ex); |
|||
} |
|||
} |
|||
} |
|||
catch (Exception exception) |
|||
{ |
|||
Logger.Error(this, "Query", exception.GetType().FullName, exception.Message, sql); |
|||
return new Query(exception); |
|||
} |
|||
} |
|||
|
|||
/// <summary>执行。</summary>
|
|||
public IExecute Execute(string sql) => Execute(sql, null); |
|||
|
|||
/// <summary>执行单条 Transact-SQL 语句,并加入参数。</summary>
|
|||
public IExecute Execute(string sql, IEnumerable<IDataParameter> parameters) |
|||
{ |
|||
if (TextUtility.IsEmpty(sql)) return new Execute(false, "语句无效。"); |
|||
|
|||
var connected = Connect(); |
|||
if (connected.NotEmpty()) return new Execute(false, connected); |
|||
|
|||
var inTransaction = _transaction != null; |
|||
if (!inTransaction) Begin(); |
|||
try |
|||
{ |
|||
|
|||
using (var command = NewCommand()) |
|||
{ |
|||
command.Connection = _conn; |
|||
command.Transaction = (DbTransaction)_transaction; |
|||
if (Timeout != null) command.CommandTimeout = Timeout.Execute; |
|||
command.CommandText = sql; |
|||
if (parameters != null) |
|||
{ |
|||
foreach (var parameter in parameters) |
|||
{ |
|||
if (parameter != null) command.Parameters.Add(parameter); |
|||
} |
|||
} |
|||
var rows = command.ExecuteNonQuery(); |
|||
if (!inTransaction) Commit(); // todo 此处应该检查事务提交产生的错误。
|
|||
return new Execute(true, rows); |
|||
} |
|||
} |
|||
catch (Exception exception) |
|||
{ |
|||
Logger.Error(this, "Execute", exception, sql); |
|||
if (!inTransaction) Rollback(); |
|||
return new Execute(exception); |
|||
} |
|||
} |
|||
|
|||
/// <summary>输出查询结果的首列数据。</summary>
|
|||
protected string[] TextColumn(string sql, string[] excluded = null) |
|||
{ |
|||
if (Connect().NotEmpty()) return new string[0]; |
|||
using (var query = Query(sql)) |
|||
{ |
|||
var rows = query.Rows; |
|||
var list = new List<string>(rows); |
|||
for (int r = 0; r < query.Rows; r++) |
|||
{ |
|||
var cell = query.Text(r, 0); |
|||
if (TextUtility.IsEmpty(cell)) continue; |
|||
if (excluded != null && excluded.Contains(cell)) continue; |
|||
list.Add(cell); |
|||
} |
|||
return list.ToArray(); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region orm
|
|||
|
|||
/// <summary>使用指定语句查询,获取查询结果。</summary>
|
|||
/// <param name="sql">要执行的 SQL 语句。</param>
|
|||
/// <param name="parameters">为 SQL 语句提供的参数。</param>
|
|||
public Result<T[]> Query<T>(string sql, IEnumerable<IDataParameter> parameters = null) where T : class, new() => OrmHelper.As<object, T>(Query(typeof(T), sql, parameters)); |
|||
|
|||
/// <summary>使用指定语句查询,获取查询结果。</summary>
|
|||
/// <param name="model">目标记录的类型。</param>
|
|||
/// <param name="sql">要执行的 SQL 语句。</param>
|
|||
/// <param name="parameters">为 SQL 语句提供的参数。</param>
|
|||
public Result<object[]> Query(Type model, string sql, IEnumerable<IDataParameter> parameters = null) |
|||
{ |
|||
if (_conn == null) return new Result<object[]>("连接无效。"); |
|||
if (model == null) return new Result<object[]>("数据模型类型无效。"); |
|||
if (string.IsNullOrEmpty(sql)) return new Result<object[]>("SQL 语句无效。"); |
|||
|
|||
using (var query = Query(sql, parameters)) |
|||
{ |
|||
var result = null as Result<object[]>; |
|||
if (query.Success) |
|||
{ |
|||
try |
|||
{ |
|||
var array = OrmHelper.Fill(query, model); |
|||
result = new Result<object[]>(array); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
result = new Result<object[]>(ex); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
result = new Result<object[]>(query.Message); |
|||
} |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
#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.Bytes(Encoding.UTF8).Length > bytes) |
|||
{ |
|||
while (true) |
|||
{ |
|||
t = t.Substring(0, t.Length - 1); |
|||
if (t.Bytes(Encoding.UTF8).Length <= (bytes - 4)) break; |
|||
} |
|||
t = t + " ..."; |
|||
} |
|||
} |
|||
|
|||
return t; |
|||
} |
|||
|
|||
/// <summary>获取表名。</summary>
|
|||
protected static string Table<T>() => Table(typeof(T)); |
|||
|
|||
/// <summary>获取表名。</summary>
|
|||
protected static string Table(Type model) |
|||
{ |
|||
if (model == null) throw new Exception($"无法从无效类型获取表名。"); |
|||
|
|||
var ts = TableStructure.Parse(model); |
|||
if (ts == null) throw new Exception($"无法从类型 {model.FullName} 获取表名。"); |
|||
|
|||
return ts.Name; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region derived
|
|||
|
|||
/// <summary>为 Ado 创建连接字符串。</summary>
|
|||
protected abstract string NewConnectionString(); |
|||
|
|||
/// <summary>为 Ado 创建 IDbConnection 对象。</summary>
|
|||
protected abstract IDbConnection NewConnection(); |
|||
|
|||
/// <summary>为 Ado 创建 IDbCommand 对象。</summary>
|
|||
protected abstract IDbCommand NewCommand(); |
|||
|
|||
/// <summary>为 Ado 创建 IDataAdapter 对象。</summary>
|
|||
protected abstract IDataAdapter NewDataAdapter(IDbCommand command); |
|||
|
|||
// /// <summary>为 Ado 创建 IDataParameter 对象。</summary>
|
|||
// protected abstract IDataParameter NewDataParameter();
|
|||
|
|||
#endregion
|
|||
|
|||
#region initialization
|
|||
|
|||
/// <summary>查询数据库中的所有表名。</summary>
|
|||
public abstract string[] TableNames(); |
|||
|
|||
/// <summary>查询数据库实例中的所有数据库名。</summary>
|
|||
public abstract string[] StoreNames(); |
|||
|
|||
/// <summary>查询表中的所有列名。</summary>
|
|||
public abstract string[] ColumnNames(string tableName); |
|||
|
|||
/// <summary>获取列信息。</summary>
|
|||
public abstract ColumnInfo[] ColumnsInfo(string tableName); |
|||
|
|||
/// <summary>初始化指定类型,以创建表或增加字段。</summary>
|
|||
/// <returns>错误信息。当成功时候返回空字符串。</returns>
|
|||
public string Initialize<T>() where T : class, new() => Initialize(typeof(T)); |
|||
|
|||
#endregion
|
|||
|
|||
#region IDbClientOrm
|
|||
|
|||
/// <summary>初始化指定类型,以创建表或增加字段。</summary>
|
|||
/// <param name="model">要初始化的类型。</param>
|
|||
/// <returns>错误信息。当成功时候返回空字符串。</returns>
|
|||
public abstract string Initialize(Type model); |
|||
|
|||
/// <summary>插入记录。</summary>
|
|||
/// <param name="record">要插入的记录实体。</param>
|
|||
/// <param name="table">插入到指定表。当不指定时,由 record 类型决定。</param>
|
|||
/// <returns>错误信息。当成功时候返回空字符串。</returns>
|
|||
public abstract string Insert(object record, string table = null); |
|||
|
|||
/// <summary>更新记录。</summary>
|
|||
/// <param name="record">要插入的记录实体。</param>
|
|||
/// <param name="table">插入到指定表。当不指定时,由 record 类型决定。</param>
|
|||
/// <returns>错误信息。当成功时候返回空字符串。</returns>
|
|||
public abstract string Update(IRecord record, string table = null); |
|||
|
|||
/// <summary>获取指定类型的主键,按 Flag 属性筛选。</summary>
|
|||
/// <param name="model">要查询的类型。</param>
|
|||
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
|
|||
public abstract Result<string[]> Keys(Type model, long flag = 0); |
|||
|
|||
/// <summary>获取指定类型的主键,按 Flag 属性筛选。</summary>
|
|||
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
|
|||
public abstract Result<string[]> Keys<T>(long flag = 0) where T : class, IRecord, new(); |
|||
|
|||
/// <summary>获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。</summary>
|
|||
/// <param name="model">目标记录的类型。</param>
|
|||
/// <param name="key">目标记录的主键。</param>
|
|||
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
|
|||
public abstract Result<object> Get(Type model, string key, long flag = 0); |
|||
|
|||
/// <summary>获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。</summary>
|
|||
/// <param name="key">目标记录的主键。</param>
|
|||
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
|
|||
public abstract Result<T> Get<T>(string key, long flag = 0) where T : class, IRecord, new(); |
|||
|
|||
/// <summary>查询所有记录。</summary>
|
|||
/// <param name="model">目标记录的类型。</param>
|
|||
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
|
|||
public abstract Result<object[]> Query(Type model, long flag = 0); |
|||
|
|||
/// <summary>查询所有记录。</summary>
|
|||
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
|
|||
public abstract Result<T[]> Query<T>(long flag = 0) where T : class, IRecord, new(); |
|||
|
|||
#endregion
|
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
#endif
|
@ -0,0 +1,528 @@ |
|||
#if DEBUG
|
|||
|
|||
/* 2021.11.28 */ |
|||
|
|||
using Apewer; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Data; |
|||
using System.Data.Common; |
|||
using System.Text; |
|||
|
|||
using static Apewer.Source.OrmHelper; |
|||
using System.Data.SqlClient; |
|||
|
|||
#if NETFRAMEWORK
|
|||
using System.Data.Sql; |
|||
#endif
|
|||
|
|||
namespace Apewer.Source |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
[Serializable] |
|||
|
|||
public class SqlClientThin : DbClient, IDbClient |
|||
{ |
|||
|
|||
#region
|
|||
|
|||
string _str = null; |
|||
|
|||
/// <summary>使用连接字符串创建数据库连接实例。</summary>
|
|||
public SqlClientThin(string connectionString, Timeout timeout = null) : base(timeout) |
|||
{ |
|||
_str = connectionString ?? ""; |
|||
} |
|||
|
|||
/// <summary>使用连接凭据创建数据库连接实例。</summary>
|
|||
public SqlClientThin(string address, string store, string user, string pass, Timeout timeout = null) : base(timeout) |
|||
{ |
|||
if (timeout == null) timeout = Timeout.Default; |
|||
|
|||
var a = address ?? ""; |
|||
var s = store ?? ""; |
|||
var u = user ?? ""; |
|||
var p = pass ?? ""; |
|||
var cs = $"data source = {a}; initial catalog = {s}; "; |
|||
if (string.IsNullOrEmpty(u)) cs += "integrated security = sspi; "; |
|||
else |
|||
{ |
|||
cs += $"user id = {u}; "; |
|||
if (!string.IsNullOrEmpty(p)) cs += $"password = {p}; "; |
|||
} |
|||
cs += $"connection timeout = {timeout.Connect}; "; |
|||
|
|||
_str = cs; |
|||
} |
|||
|
|||
/// <summary>为 Ado 创建连接字符串。</summary>
|
|||
protected override string NewConnectionString() => _str; |
|||
|
|||
/// <summary>为 Ado 创建 IDbConnection 对象。</summary>
|
|||
protected override IDbConnection NewConnection() => new SqlConnection(); |
|||
|
|||
/// <summary>为 Ado 创建 IDbCommand 对象。</summary>
|
|||
protected override IDbCommand NewCommand() => new SqlCommand(); |
|||
|
|||
/// <summary>为 Ado 创建 IDataAdapter 对象。</summary>
|
|||
protected override IDataAdapter NewDataAdapter(IDbCommand command) => new SqlDataAdapter((SqlCommand)command); |
|||
|
|||
#endregion
|
|||
|
|||
#region ORM
|
|||
|
|||
/// <summary>查询数据库中的所有表名。</summary>
|
|||
public override string[] TableNames() => TextColumn("select [name] from [sysobjects] where [type] = 'u' order by [name]; "); |
|||
|
|||
/// <summary>查询数据库实例中的所有数据库名。</summary>
|
|||
public override string[] StoreNames() => TextColumn("select [name] from [master]..[sysdatabases] order by [name]; ", new string[] { "master", "model", "msdb", "tempdb" }); |
|||
|
|||
/// <summary>查询表中的所有列名。</summary>
|
|||
public override string[] ColumnNames(string tableName) => TextColumn($"select [name] from [syscolumns] where [id] = object_id('{tableName}'); "); |
|||
|
|||
/// <summary>获取列信息。</summary>
|
|||
public override ColumnInfo[] ColumnsInfo(string tableName) |
|||
{ |
|||
if (tableName.IsEmpty()) throw new ArgumentNullException(nameof(tableName)); |
|||
var sql = $"select name, xtype, length from syscolumns where id = object_id('{tableName}') "; |
|||
using (var query = Query(sql)) |
|||
{ |
|||
var ab = new ArrayBuilder<ColumnInfo>(); |
|||
for (var i = 0; i < query.Rows; i++) |
|||
{ |
|||
var info = new ColumnInfo(); |
|||
info.Name = query.Text(i, "name"); |
|||
info.Type = XType(query.Int32(i, "xtype")); |
|||
info.Length = query.Int32(i, "length"); |
|||
ab.Add(info); |
|||
} |
|||
return ab.Export(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。</summary>
|
|||
public override string Initialize(Type model) |
|||
{ |
|||
var structure = TableStructure.Parse(model); |
|||
if (structure == null) return "无法解析记录模型。"; |
|||
|
|||
// 连接数据库。
|
|||
var connect = Connect(); |
|||
if (connect.NotEmpty()) return connect; |
|||
|
|||
// 检查现存表。
|
|||
var exists = false; |
|||
var tables = TableNames(); |
|||
if (tables.Length > 0) |
|||
{ |
|||
var lower = structure.Name.ToLower(); |
|||
foreach (var table in tables) |
|||
{ |
|||
if (TextUtility.IsBlank(table)) continue; |
|||
if (table.ToLower() == lower) |
|||
{ |
|||
exists = true; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (exists) |
|||
{ |
|||
// 获取已存在的列名。
|
|||
var columns = ColumnNames(structure.Name); |
|||
if (columns.Length > 0) |
|||
{ |
|||
var lower = new List<string>(); |
|||
foreach (var column in columns) |
|||
{ |
|||
if (TextUtility.IsBlank(column)) continue; |
|||
lower.Add(column.ToLower()); |
|||
} |
|||
columns = lower.ToArray(); |
|||
} |
|||
|
|||
// 增加列。
|
|||
foreach (var column in structure.Columns) |
|||
{ |
|||
// 检查 Independent 特性。
|
|||
if (structure.Independent && column.Independent) continue; |
|||
|
|||
// 去重。
|
|||
var lower = column.Field.ToLower(); |
|||
if (columns.Contains(lower)) continue; |
|||
|
|||
var type = Declaration(column); |
|||
if (string.IsNullOrEmpty(type)) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); |
|||
|
|||
var sql = TextUtility.Merge("alter table [", structure.Name, "] add ", type, "; "); |
|||
var execute = Execute(sql); |
|||
if (execute.Success == false) return execute.Message; |
|||
} |
|||
return TextUtility.Empty; |
|||
} |
|||
else |
|||
{ |
|||
var sqlcolumns = new List<string>(); |
|||
foreach (var column in structure.Columns) |
|||
{ |
|||
// 检查 Independent 特性。
|
|||
if (structure.Independent && column.Independent) continue; |
|||
|
|||
var type = Declaration(column); |
|||
if (!column.Independent && column.Property.Name == "Key") type = type + " primary key"; |
|||
|
|||
if (string.IsNullOrEmpty(type)) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); |
|||
sqlcolumns.Add(type); |
|||
} |
|||
var sql = TextUtility.Merge("create table [", structure.Name, "](", string.Join(", ", sqlcolumns.ToArray()), "); "); |
|||
var execute = Execute(sql); |
|||
if (execute.Success) return TextUtility.Empty; |
|||
return execute.Message; |
|||
} |
|||
} |
|||
|
|||
/// <summary>插入记录。返回错误信息。</summary>
|
|||
public override string Insert(object record, string table = null) |
|||
{ |
|||
if (record == null) return "参数无效。"; |
|||
FixProperties(record); |
|||
|
|||
var structure = TableStructure.Parse(record.GetType()); |
|||
if (structure == null) return "无法解析记录模型。"; |
|||
if (string.IsNullOrEmpty(table)) table = structure.Name; |
|||
if (string.IsNullOrEmpty(table)) return "表名称无效。"; |
|||
|
|||
var ps = structure.CreateParameters(record, Parameter); |
|||
var psc = ps.Length; |
|||
if (psc < 1) return "数据模型不包含字段。"; |
|||
|
|||
var names = new List<string>(psc); |
|||
var values = new List<string>(psc); |
|||
foreach (var column in ps) |
|||
{ |
|||
//names.Add(TextGenerator.Merge("[", column, "]"));
|
|||
names.Add(TextUtility.Merge(column)); |
|||
values.Add("@" + column); |
|||
} |
|||
var sb = new StringBuilder(); |
|||
sb.Append("insert into [", table, "](", string.Join(", ", names.ToArray()), ") "); |
|||
sb.Append("values(", string.Join(", ", values.ToArray()), "); "); |
|||
var sql = sb.ToString(); |
|||
|
|||
var execute = Execute(sql, ps); |
|||
if (execute.Success) return TextUtility.Empty; |
|||
return execute.Message; |
|||
} |
|||
|
|||
/// <summary>更新记录,实体中的 Key 属性不被更新。返回错误信息。</summary>
|
|||
/// <remarks>无法更新带有 Independent 特性的模型(缺少 Key 属性)。</remarks>
|
|||
public override string Update(IRecord record, string table = null) |
|||
{ |
|||
if (record == null) return "参数无效。"; |
|||
FixProperties(record); |
|||
SetUpdated(record); |
|||
|
|||
var structure = TableStructure.Parse(record.GetType()); |
|||
if (structure == null) return "无法解析记录模型。"; |
|||
if (structure.Independent) return "无法更新带有 Independent 特性的模型。"; |
|||
if (string.IsNullOrEmpty(table)) table = structure.Name; |
|||
if (string.IsNullOrEmpty(table)) return "表名称无效。"; |
|||
|
|||
var ps = structure.CreateParameters(record, Parameter, "_key"); |
|||
var psc = ps.Length; |
|||
if (psc < 1) return "数据模型不包含字段。"; |
|||
|
|||
var items = new List<string>(); |
|||
foreach (var p in ps) |
|||
{ |
|||
var pn = p.ParameterName; |
|||
items.Add(TextUtility.Merge("[", pn, "] = @", pn)); |
|||
} |
|||
var key = record.Key.SafeKey(); |
|||
var sql = TextUtility.Merge("update [", table, "] set ", string.Join(", ", items.ToArray()), " where [_key]='", key, "'; "); |
|||
|
|||
var execute = Execute(sql, ps); |
|||
if (execute.Success) return TextUtility.Empty; |
|||
return execute.Message; |
|||
} |
|||
|
|||
/// <summary>获取记录。</summary>
|
|||
public override Result<object[]> Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => |
|||
{ |
|||
if (flag == 0) return $"select * from [{tn}]; "; |
|||
return $"select * from [{tn}] where _flag={flag}; "; |
|||
}); |
|||
|
|||
/// <summary>获取记录。</summary>
|
|||
public override Result<T[]> Query<T>(long flag = 0) => OrmHelper.Query<T>(this, (tn) => |
|||
{ |
|||
if (flag == 0) return $"select * from [{tn}]; "; |
|||
return $"select * from [{tn}] where _flag={flag}; "; |
|||
}); |
|||
|
|||
/// <summary>获取具有指定 Key 的记录。</summary>
|
|||
public override Result<object> Get(Type model, string key, long flag = 0) => OrmHelper.Get(this, model, key, (tn, sk) => |
|||
{ |
|||
if (flag == 0) return $"select top 1 * from [{tn}] _key='{sk}'; "; |
|||
return $"select top 1 * from [{tn}] where _key='{sk}' and _key='{sk}'; "; |
|||
}); |
|||
|
|||
/// <summary>获取具有指定 Key 的记录。</summary>
|
|||
public override Result<T> Get<T>(string key, long flag = 0) => OrmHelper.Get<T>(this, key, (tn, sk) => |
|||
{ |
|||
if (flag == 0) return $"select top 1 * from [{tn}] _key='{sk}'; "; |
|||
return $"select top 1 * from [{tn}] where _key='{sk}' and _key='{sk}'; "; |
|||
}); |
|||
|
|||
/// <summary>查询有效的 Key 值。</summary>
|
|||
public override Result<string[]> Keys(Type model, long flag = 0) => OrmHelper.Keys(this, model, (tn) => |
|||
{ |
|||
if (flag == 0) return $"select _key from [{tn}]; "; |
|||
return $"select _key from [{tn}] where _flag={flag}; "; |
|||
}); |
|||
|
|||
/// <summary>查询有效的 Key 值。</summary>
|
|||
public override Result<string[]> Keys<T>(long flag = 0) => Keys(typeof(T), flag); |
|||
|
|||
#endregion
|
|||
|
|||
#region public static
|
|||
|
|||
#if NET20 || NET40
|
|||
|
|||
/// <summary>枚举本地网络中服务器的名称。</summary>
|
|||
public static SqlServerSource[] EnumerateServer() |
|||
{ |
|||
var list = new List<SqlServerSource>(); |
|||
|
|||
// 表中列名:ServerName、InstanceName、IsClustered、Version。
|
|||
using (var query = new Query(SqlDataSourceEnumerator.Instance.GetDataSources())) |
|||
{ |
|||
for (int i = 0; i < query.Rows; i++) |
|||
{ |
|||
var item = new SqlServerSource(); |
|||
item.ServerName = query.Text(i, "ServerName"); |
|||
list.Add(item); |
|||
} |
|||
} |
|||
return list.ToArray(); |
|||
} |
|||
|
|||
#endif
|
|||
|
|||
/// <summary>指定的连接凭据是否符合连接要求,默认指定 master 数据库。</summary>
|
|||
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) |
|||
{ |
|||
var a = string.IsNullOrEmpty(address); |
|||
var s = string.IsNullOrEmpty(store); |
|||
var u = string.IsNullOrEmpty(user); |
|||
var p = string.IsNullOrEmpty(pass); |
|||
if (a) return false; |
|||
if (s) return false; |
|||
if (u && !p) return false; |
|||
return true; |
|||
} |
|||
|
|||
/// <summary>创建参数。</summary>
|
|||
/// <exception cref="ArgumentNullException"></exception>
|
|||
/// <exception cref="InvalidOperationException"></exception>
|
|||
static SqlParameter Parameter(Parameter parameter) |
|||
{ |
|||
if (parameter == null) throw new InvalidOperationException("参数无效。"); |
|||
return Parameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value); |
|||
} |
|||
|
|||
/// <summary>创建参数。</summary>
|
|||
public static SqlParameter Parameter(string name, ColumnType type, int size, object value) |
|||
{ |
|||
var vname = TextUtility.Trim(name); |
|||
if (TextUtility.IsBlank(vname)) return null; |
|||
|
|||
var vtype = SqlDbType.BigInt; |
|||
switch (type) |
|||
{ |
|||
case ColumnType.Bytes: |
|||
vtype = SqlDbType.Image; |
|||
break; |
|||
case ColumnType.Integer: |
|||
vtype = SqlDbType.BigInt; |
|||
break; |
|||
case ColumnType.Float: |
|||
vtype = SqlDbType.Float; |
|||
break; |
|||
case ColumnType.DateTime: |
|||
vtype = SqlDbType.DateTime; |
|||
break; |
|||
case ColumnType.VarChar: |
|||
case ColumnType.VarChar191: |
|||
case ColumnType.VarCharMax: |
|||
vtype = SqlDbType.VarChar; |
|||
break; |
|||
case ColumnType.NVarChar: |
|||
case ColumnType.NVarChar191: |
|||
case ColumnType.NVarCharMax: |
|||
vtype = SqlDbType.NVarChar; |
|||
break; |
|||
case ColumnType.Text: |
|||
vtype = SqlDbType.Text; |
|||
break; |
|||
case ColumnType.NText: |
|||
vtype = SqlDbType.NText; |
|||
break; |
|||
default: |
|||
throw new InvalidOperationException(TextUtility.Merge("类型 ", type.ToString(), " 不受支持。")); |
|||
} |
|||
|
|||
var vsize = size; |
|||
switch (type) |
|||
{ |
|||
case ColumnType.VarChar: |
|||
vsize = NumberUtility.Restrict(vsize, 0, 8000); |
|||
break; |
|||
case ColumnType.NVarChar: |
|||
vsize = NumberUtility.Restrict(vsize, 0, 4000); |
|||
break; |
|||
case ColumnType.VarChar191: |
|||
case ColumnType.NVarChar191: |
|||
vsize = NumberUtility.Restrict(vsize, 0, 191); |
|||
break; |
|||
default: |
|||
vsize = 0; |
|||
break; |
|||
} |
|||
|
|||
var vvalue = value; |
|||
if (vvalue is string && vvalue != null && vsize > 0) |
|||
{ |
|||
vvalue = TextUtility.Left((string)vvalue, vsize); |
|||
} |
|||
|
|||
var parameter = new SqlParameter(); |
|||
parameter.ParameterName = vname; |
|||
parameter.SqlDbType = vtype; |
|||
parameter.Value = vvalue; |
|||
if (vsize > 0) parameter.Size = vsize; |
|||
return parameter; |
|||
} |
|||
|
|||
/// <summary>创建参数。</summary>
|
|||
public static SqlParameter Parameter(string name, SqlDbType type, int size, object value) |
|||
{ |
|||
if (value is string && value != null && size > 0) |
|||
{ |
|||
value = TextUtility.Left((string)value, (int)size); |
|||
} |
|||
|
|||
var p = new SqlParameter(); |
|||
p.ParameterName = name ?? ""; |
|||
p.SqlDbType = type; |
|||
p.Size = size; |
|||
p.Value = value; |
|||
return p; |
|||
} |
|||
|
|||
/// <summary>创建参数。</summary>
|
|||
public static SqlParameter Parameter(string name, SqlDbType type, object value) |
|||
{ |
|||
var p = new SqlParameter(); |
|||
p.ParameterName = name ?? ""; |
|||
p.SqlDbType = type; |
|||
p.Value = value; |
|||
return p; |
|||
} |
|||
|
|||
static string Declaration(ColumnAttribute column) |
|||
{ |
|||
var type = TextUtility.Empty; |
|||
var vcolumn = column; |
|||
var length = Math.Max(0, vcolumn.Length); |
|||
switch (vcolumn.Type) |
|||
{ |
|||
case ColumnType.Integer: |
|||
type = "bigint"; |
|||
break; |
|||
case ColumnType.Float: |
|||
type = "float"; |
|||
break; |
|||
case ColumnType.Bytes: |
|||
type = "image"; |
|||
break; |
|||
case ColumnType.DateTime: |
|||
type = "datetime"; |
|||
break; |
|||
case ColumnType.VarChar: |
|||
type = TextUtility.Merge("varchar(", Math.Min(8000, length).ToString(), ")"); |
|||
break; |
|||
case ColumnType.VarChar191: |
|||
type = TextUtility.Merge("varchar(191)"); |
|||
break; |
|||
case ColumnType.VarCharMax: |
|||
type = TextUtility.Merge("varchar(max)"); |
|||
break; |
|||
case ColumnType.Text: |
|||
type = TextUtility.Merge("text"); |
|||
break; |
|||
case ColumnType.NVarChar: |
|||
type = TextUtility.Merge("nvarchar(", Math.Min(4000, length).ToString(), ")"); |
|||
break; |
|||
case ColumnType.NVarChar191: |
|||
type = TextUtility.Merge("nvarchar(191)"); |
|||
break; |
|||
case ColumnType.NVarCharMax: |
|||
type = TextUtility.Merge("nvarchar(max)"); |
|||
break; |
|||
case ColumnType.NText: |
|||
type = TextUtility.Merge("ntext"); |
|||
break; |
|||
default: |
|||
return TextUtility.Empty; |
|||
} |
|||
return TextUtility.Merge("[", vcolumn.Field, "] ", type); |
|||
} |
|||
|
|||
static string XType(int xtype) |
|||
{ |
|||
switch (xtype) |
|||
{ |
|||
case 34: return "image"; |
|||
case 35: return "text"; |
|||
case 36: return "uniqueidentifier"; |
|||
case 48: return "tinyint"; |
|||
case 52: return "smallint"; |
|||
case 56: return "int"; |
|||
case 58: return "smalldatetime"; |
|||
case 59: return "real"; |
|||
case 60: return "money"; |
|||
case 61: return "datetime"; |
|||
case 62: return "float"; |
|||
case 98: return "sql_variant"; |
|||
case 99: return "ntext"; |
|||
case 104: return "bit"; |
|||
case 106: return "decimal"; |
|||
case 108: return "numeric"; |
|||
case 122: return "smallmoney"; |
|||
case 127: return "bigint"; |
|||
case 165: return "varbinary"; |
|||
case 167: return "varchar"; |
|||
case 173: return "binary"; |
|||
case 175: return "char"; |
|||
case 189: return "timestamp"; |
|||
case 231: return "nvarchar"; |
|||
case 239: return "nchar"; |
|||
case 241: return "xml"; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
#endif
|
@ -0,0 +1,35 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.Network |
|||
{ |
|||
|
|||
/// <summary>套接字数据接收模型。</summary>
|
|||
[Serializable] |
|||
public sealed class SocketReceived |
|||
{ |
|||
|
|||
/// <summary>对端 IP 地址。</summary>
|
|||
public string IP { get; set; } |
|||
|
|||
/// <summary>对端端口。</summary>
|
|||
public int Port { get; set; } |
|||
|
|||
/// <summary>对端数据。</summary>
|
|||
public byte[] Bytes { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public SocketReceived() { } |
|||
|
|||
/// <summary></summary>
|
|||
public SocketReceived(string ip, int port, byte[] bytes) |
|||
{ |
|||
IP = ip; |
|||
Port = port; |
|||
Bytes = bytes; |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,129 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Net; |
|||
using System.Net.Sockets; |
|||
using System.Security; |
|||
using System.Text; |
|||
using System.Threading; |
|||
|
|||
namespace Apewer.Network |
|||
{ |
|||
|
|||
/// <summary>TCP 端口代理。</summary>
|
|||
public class TcpProxy |
|||
{ |
|||
|
|||
IPEndPoint _local; |
|||
Socket _listen; |
|||
Thread _thread; |
|||
int _backlog; |
|||
int _port; |
|||
|
|||
Func<IPEndPoint, IPEndPoint> _remote; |
|||
|
|||
/// <summary>本地端口已连接。</summary>
|
|||
public bool Connected { get => _listen == null ? false : _listen.Connected; } |
|||
|
|||
/// <summary>本地已监听的端口。</summary>
|
|||
public int Port { get => _port; } |
|||
|
|||
/// <summary>监听本地端口以启动代理。</summary>
|
|||
/// <param name="local">本地监听端口。</param>
|
|||
/// <param name="remote">获取要连接的远程端口,无法获取远程端口时应返回 NULL 值。</param>
|
|||
/// <param name="backlog">挂起连接队列的最大长度</param>
|
|||
/// <exception cref="ArgumentNullException"></exception>
|
|||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|||
/// <exception cref="SocketException"></exception>
|
|||
/// <exception cref="SecurityException"></exception>
|
|||
public TcpProxy(IPEndPoint local, Func<IPEndPoint, IPEndPoint> remote, int backlog = 10000) |
|||
{ |
|||
Start(local, remote, backlog); |
|||
} |
|||
|
|||
void Start(IPEndPoint local, Func<IPEndPoint, IPEndPoint> remote, int backlog = 10000) |
|||
{ |
|||
if (local == null) throw new ArgumentNullException(nameof(local)); |
|||
if (remote == null) throw new ArgumentNullException(nameof(remote)); |
|||
if (backlog < 1) throw new ArgumentOutOfRangeException(nameof(backlog)); |
|||
|
|||
_local = local; |
|||
_remote = remote; |
|||
_backlog = backlog; |
|||
|
|||
var server = new Socket(_local.AddressFamily, SocketType.Stream, ProtocolType.Tcp); |
|||
server.Bind(_local); |
|||
server.Listen(_backlog); |
|||
_listen = server; |
|||
if (server.LocalEndPoint is IPEndPoint lep) _port = lep.Port; |
|||
|
|||
_thread = new Thread(Listen); |
|||
_thread.IsBackground = true; |
|||
_thread.Start(); |
|||
} |
|||
|
|||
/// <summary>停止代理。</summary>
|
|||
public void Stop() |
|||
{ |
|||
Close(_listen); |
|||
} |
|||
|
|||
void Listen() |
|||
{ |
|||
while (true) |
|||
{ |
|||
if (_listen == null) break; |
|||
Socket socket1 = null; |
|||
IPEndPoint remote = null; |
|||
try |
|||
{ |
|||
socket1 = _listen.Accept(); |
|||
remote = _remote(socket1.RemoteEndPoint as IPEndPoint); |
|||
} |
|||
catch { } |
|||
if (socket1 == null || remote == null) continue; |
|||
|
|||
Socket socket2 = null; |
|||
try |
|||
{ |
|||
socket2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); |
|||
socket2.Connect(remote); |
|||
ThreadPool.QueueUserWorkItem(Handler, new Socket[] { socket1, socket2 }); |
|||
ThreadPool.QueueUserWorkItem(Handler, new Socket[] { socket2, socket1 }); |
|||
} |
|||
catch |
|||
{ |
|||
Close(socket1); |
|||
Close(socket2); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void Handler(object obj) |
|||
{ |
|||
var tuple = (Socket[])obj; |
|||
var src = tuple[0]; |
|||
var dst = tuple[1]; |
|||
var buffer = new byte[1024]; |
|||
while (true) |
|||
{ |
|||
try |
|||
{ |
|||
int count = src.Receive(buffer, buffer.Length, SocketFlags.None); |
|||
if (count < 1) break; |
|||
dst.Send(buffer, count, SocketFlags.None); |
|||
} |
|||
catch { break; } |
|||
} |
|||
Close(src); |
|||
Close(dst); |
|||
} |
|||
|
|||
static void Close(Socket socket) |
|||
{ |
|||
if (socket == null) return; |
|||
try { socket.Close(); } catch { } |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
@ -1,21 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Net.Sockets; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.Network |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public delegate void SocketReceivedEventHandler(object sender, string ip, int port, byte[] bytes); |
|||
|
|||
/// <summary></summary>
|
|||
public delegate void SocketEndPointEventHandler(object sender, string ip, int port); |
|||
|
|||
///// <summary></summary>
|
|||
//public delegate void SocketExceptionCallback(object argSender, SocketException value);
|
|||
|
|||
///// <summary>返回 Http Progress 对象。</summary>
|
|||
//public delegate void HttpProgressCallback(object argSender, HttpProgress argValue);
|
|||
|
|||
} |
@ -1,339 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Data; |
|||
using System.Data.Common; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.Source |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
abstract class DbClient |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public virtual Logger Logger { get; set; } |
|||
|
|||
#region Connection
|
|||
|
|||
DbConnection _conn = null; |
|||
string _str = null; |
|||
|
|||
/// <summary></summary>
|
|||
public Timeout Timeout { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public DbConnection Connection { get => _conn; } |
|||
|
|||
/// <summary></summary>
|
|||
public bool Online { get => _conn == null ? false : (_conn.State == ConnectionState.Open); } |
|||
|
|||
/// <summary>连接字符串。</summary>
|
|||
public string ConnectionString { get => _str; } |
|||
|
|||
/// <summary></summary>
|
|||
public virtual string Connect() |
|||
{ |
|||
if (_conn == null) |
|||
{ |
|||
_str = GetConnectionString(); |
|||
_conn = NewConnection(); |
|||
_conn.ConnectionString = _str; |
|||
} |
|||
else |
|||
{ |
|||
if (_conn.State == ConnectionState.Open) return null; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
_conn.Open(); |
|||
switch (_conn.State) |
|||
{ |
|||
case ConnectionState.Open: return null; |
|||
default: return $"连接失败,当前处于 {_conn.State} 状态。"; |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Logger.Error(this, "Connect", ex, _conn.ConnectionString); |
|||
Close(); |
|||
return ex.Message; |
|||
} |
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
public void Close() |
|||
{ |
|||
if (_conn != null) |
|||
{ |
|||
if (_transaction != null) |
|||
{ |
|||
if (_autocommit) Commit(); |
|||
else Rollback(); |
|||
} |
|||
_conn.Close(); |
|||
_conn.Dispose(); |
|||
_conn = null; |
|||
} |
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
public void Dispose() { Close(); } |
|||
|
|||
#endregion
|
|||
|
|||
#region Transaction
|
|||
|
|||
private DbTransaction _transaction = null; |
|||
private bool _autocommit = false; |
|||
|
|||
/// <summary>启动事务。</summary>
|
|||
public string Begin(bool commit = true) => Begin(commit, null); |
|||
|
|||
/// <summary>启动事务。</summary>
|
|||
public string Begin(bool commit, Class<System.Data.IsolationLevel> isolation) |
|||
{ |
|||
if (Connect() != null) return "未连接。"; |
|||
if (_transaction != null) return "存在已启动的事务,无法再次启动。"; |
|||
try |
|||
{ |
|||
_transaction = isolation ? _conn.BeginTransaction(isolation.Value) : _conn.BeginTransaction(); |
|||
_autocommit = commit; |
|||
return null; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Logger.Error(this, "Commit", ex.Message()); |
|||
return ex.Message(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>提交事务。</summary>
|
|||
public string Commit() |
|||
{ |
|||
if (_transaction == null) return "事务不存在。"; |
|||
try |
|||
{ |
|||
_transaction.Commit(); |
|||
RuntimeUtility.Dispose(_transaction); |
|||
_transaction = null; |
|||
return null; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
RuntimeUtility.Dispose(_transaction); |
|||
_transaction = null; |
|||
Logger.Error(this, "Commit", ex.Message()); |
|||
return ex.Message(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>从挂起状态回滚事务。</summary>
|
|||
public string Rollback() |
|||
{ |
|||
if (_transaction == null) return "事务不存在。"; |
|||
try |
|||
{ |
|||
_transaction.Rollback(); |
|||
RuntimeUtility.Dispose(_transaction); |
|||
_transaction = null; |
|||
return null; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
RuntimeUtility.Dispose(_transaction); |
|||
_transaction = null; |
|||
Logger.Error(this, "Rollback", ex.Message()); |
|||
return ex.Message(); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region ADO
|
|||
|
|||
/// <summary>查询。</summary>
|
|||
public IQuery Query(string sql) => Query(sql, null); |
|||
|
|||
/// <summary>查询。</summary>
|
|||
public IQuery Query(string sql, IEnumerable<IDataParameter> parameters) |
|||
{ |
|||
if (TextUtility.IsBlank(sql)) return new Query(false, "语句无效。"); |
|||
var connected = Connect(); |
|||
if (connected.NotEmpty()) return new Query(false, connected); |
|||
|
|||
try |
|||
{ |
|||
using (var command = NewCommand()) |
|||
{ |
|||
command.Connection = _conn; |
|||
if (Timeout != null) command.CommandTimeout = Timeout.Query; |
|||
command.CommandText = sql; |
|||
if (parameters != null) |
|||
{ |
|||
foreach (var parameter in parameters) |
|||
{ |
|||
if (parameter != null) command.Parameters.Add(parameter); |
|||
} |
|||
} |
|||
using (var ds = new DataSet()) |
|||
{ |
|||
using (var da = NewDataAdapter(sql)) |
|||
{ |
|||
var name = "table_" + Guid.NewGuid().ToString("n"); |
|||
da.Fill(ds, name); |
|||
var table = ds.Tables[name]; |
|||
return new Query(table, true); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
catch (Exception exception) |
|||
{ |
|||
Logger.Error(this, "Query", exception, sql); |
|||
return new Query(exception); |
|||
} |
|||
} |
|||
|
|||
/// <summary>执行。</summary>
|
|||
public IExecute Execute(string sql) => Execute(sql, null); |
|||
|
|||
/// <summary>执行单条 Transact-SQL 语句,并加入参数。</summary>
|
|||
public IExecute Execute(string sql, IEnumerable<IDataParameter> parameters) |
|||
{ |
|||
if (TextUtility.IsBlank(sql)) return Example.InvalidExecuteStatement; |
|||
|
|||
var connected = Connect(); |
|||
if (connected.NotEmpty()) return new Execute(false, connected); |
|||
|
|||
var inTransaction = _transaction != null; |
|||
if (!inTransaction) Begin(); |
|||
try |
|||
{ |
|||
using (var command = NewCommand()) |
|||
{ |
|||
command.Connection = _conn; |
|||
command.Transaction = (DbTransaction)_transaction; |
|||
if (Timeout != null) command.CommandTimeout = Timeout.Execute; |
|||
command.CommandText = sql; |
|||
if (parameters != null) |
|||
{ |
|||
foreach (var parameter in parameters) |
|||
{ |
|||
if (parameter != null) command.Parameters.Add(parameter); |
|||
} |
|||
} |
|||
var rows = command.ExecuteNonQuery(); |
|||
if (!inTransaction) Commit(); // todo 此处应该检查事务提交产生的错误。
|
|||
return new Execute(true, rows); |
|||
} |
|||
} |
|||
catch (Exception exception) |
|||
{ |
|||
Logger.Error(this, "Execute", exception, sql); |
|||
if (!inTransaction) Rollback(); |
|||
return new Execute(exception); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region ORM - Query
|
|||
|
|||
/// <summary>查询记录。</summary>
|
|||
/// <param name="model">记录模型。</param>
|
|||
/// <param name="sql">SQL 语句。</param>
|
|||
public Result<object[]> Query(Type model, string sql) |
|||
{ |
|||
if (_conn == null) return new Result<object[]>("连接无效。"); |
|||
if (model == null) return new Result<object[]>("数据模型类型无效。"); |
|||
if (string.IsNullOrEmpty(sql)) return new Result<object[]>("SQL 语句无效。"); |
|||
|
|||
var query = Query(sql); |
|||
var result = null as Result<object[]>; |
|||
|
|||
if (query.Success) |
|||
{ |
|||
try |
|||
{ |
|||
var array = OrmHelper.Fill(query, model); |
|||
result = new Result<object[]>(array); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
result = new Result<object[]>(ex); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
result = new Result<object[]>(query.Message); |
|||
} |
|||
|
|||
query.Dispose(); |
|||
return result; |
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
public Result<T[]> Query<T>(string sql) where T : class, new() |
|||
{ |
|||
var query = Query(sql); |
|||
if (!query.Success) return new Result<T[]>(query.Message); |
|||
var records = query.Fill<T>(); |
|||
query.Dispose(); |
|||
|
|||
var result = new Result<T[]>(records); |
|||
return result; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region Static
|
|||
|
|||
/// <summary>对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。</summary>
|
|||
protected static string Escape(string text, int bytes = 0) |
|||
{ |
|||
if (text.IsEmpty()) return ""; |
|||
|
|||
var t = text ?? ""; |
|||
t = t.Replace("\\", "\\\\"); |
|||
t = t.Replace("'", "\\'"); |
|||
t = t.Replace("\n", "\\n"); |
|||
t = t.Replace("\r", "\\r"); |
|||
t = t.Replace("\b", "\\b"); |
|||
t = t.Replace("\t", "\\t"); |
|||
t = t.Replace("\f", "\\f"); |
|||
|
|||
if (bytes > 5) |
|||
{ |
|||
if (t.Bytes(Encoding.UTF8).Length > bytes) |
|||
{ |
|||
while (true) |
|||
{ |
|||
t = t.Substring(0, t.Length - 1); |
|||
if (t.Bytes(Encoding.UTF8).Length <= (bytes - 4)) break; |
|||
} |
|||
t = t + " ..."; |
|||
} |
|||
} |
|||
|
|||
return t; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
/// <summary></summary>
|
|||
protected abstract string GetConnectionString(); |
|||
|
|||
/// <summary></summary>
|
|||
protected abstract DbConnection NewConnection(); |
|||
|
|||
/// <summary></summary>
|
|||
protected abstract DbDataAdapter NewDataAdapter(string sql); |
|||
|
|||
/// <summary></summary>
|
|||
protected abstract DbCommand NewCommand(); |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,75 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Data; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.Source |
|||
{ |
|||
|
|||
/// <summary>数据库客户端。</summary>
|
|||
public interface IDbAdo : IDisposable |
|||
{ |
|||
|
|||
#region Connection
|
|||
|
|||
/// <summary>获取连接。</summary>
|
|||
IDbConnection Connection { get; } |
|||
|
|||
/// <summary>数据库当前在线,表示连接可用。</summary>
|
|||
bool Online { get; } |
|||
|
|||
/// <summary>连接数据库,若未连接则尝试连接,返回错误信息。</summary>
|
|||
string Connect(); |
|||
|
|||
#endregion
|
|||
|
|||
#region ADO
|
|||
|
|||
/// <summary>查询。</summary>
|
|||
IQuery Query(string statement); |
|||
|
|||
/// <summary>查询。</summary>
|
|||
IQuery Query(string statement, IEnumerable<IDataParameter> parameters); |
|||
|
|||
/// <summary>执行。</summary>
|
|||
IExecute Execute(string statement); |
|||
|
|||
/// <summary>执行。</summary>
|
|||
IExecute Execute(string statement, IEnumerable<IDataParameter> parameters); |
|||
|
|||
// /// <summary>获取当前的事务对象。</summary>
|
|||
// IDbTransaction Transaction { get; }
|
|||
|
|||
#endregion
|
|||
|
|||
#region Transaction
|
|||
|
|||
/// <summary>
|
|||
/// <para>启动事务,可指定事务锁定行为。</para>
|
|||
/// <para>Chaos<br />无法覆盖隔离级别更高的事务中的挂起的更改。</para>
|
|||
/// <para>ReadCommitted<br />在正在读取数据时保持共享锁,以避免脏读,但是在事务结束之前可以更改数据,从而导致不可重复的读取或幻像数据。</para>
|
|||
/// <para>ReadUncommitted<br />可以进行脏读,意思是说,不发布共享锁,也不接受独占锁。</para>
|
|||
/// <para>RepeatableRead<br />在查询中使用的所有数据上放置锁,以防止其他用户更新这些数据。 防止不可重复的读取,但是仍可以有幻像行。</para>
|
|||
/// <para>Serializable<br />在 System.Data.DataSet 上放置范围锁,以防止在事务完成之前由其他用户更新行或向数据集中插入行。</para>
|
|||
/// <para>Snapshot<br />通过在一个应用程序正在修改数据时存储另一个应用程序可以读取的相同数据版本来减少阻止。 表示您无法从一个事务中看到在其他事务中进行的更改,即便重新查询也是如此。</para>
|
|||
/// <para>Unspecified = -1<br />正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。</para>
|
|||
/// </summary>
|
|||
/// <param name="commit">在连接的生命周期结束时未结束事务,指定 TRUE 将自动提交,指定 FALSE 将自动回滚。</param>
|
|||
/// <param name="isolation">指定事务锁定行为,不指定时将使用默认值。</param>
|
|||
string Begin(bool commit = false, Class<IsolationLevel> isolation = null); |
|||
|
|||
/// <summary>提交事务。</summary>
|
|||
/// <remarks>异常常见于事务已经提交或连接已断开。</remarks>
|
|||
/// <returns>提交失败时返回错误信息,成功时返回 NULL 值。</returns>
|
|||
string Commit(); |
|||
|
|||
/// <summary>从挂起状态回滚事务。</summary>
|
|||
/// <remarks>异常常见于事务已经提交、已回滚或连接已断开。</remarks>
|
|||
/// <returns>提交失败时返回错误信息,成功时返回 NULL 值。</returns>
|
|||
string Rollback(); |
|||
|
|||
#endregion
|
|||
|
|||
} |
|||
|
|||
} |
@ -1,72 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Data; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.Source |
|||
{ |
|||
|
|||
/// <summary>数据库访问接口。</summary>
|
|||
public interface IDbClientAdo : IDisposable |
|||
{ |
|||
|
|||
#region Connection
|
|||
|
|||
/// <summary>获取连接。</summary>
|
|||
IDbConnection Connection { get; } |
|||
|
|||
/// <summary>数据库当前在线,表示连接可用。</summary>
|
|||
bool Online { get; } |
|||
|
|||
/// <summary>连接数据库,若未连接则尝试连接,返回错误信息。</summary>
|
|||
string Connect(); |
|||
|
|||
#endregion
|
|||
|
|||
#region SQL
|
|||
|
|||
/// <summary>查询。</summary>
|
|||
IQuery Query(string statement); |
|||
|
|||
/// <summary>查询。</summary>
|
|||
IQuery Query(string statement, IEnumerable<IDataParameter> parameters); |
|||
|
|||
/// <summary>执行。</summary>
|
|||
IExecute Execute(string statement); |
|||
|
|||
/// <summary>执行。</summary>
|
|||
IExecute Execute(string statement, IEnumerable<IDataParameter> parameters); |
|||
|
|||
// /// <summary>获取当前的事务对象。</summary>
|
|||
// IDbTransaction Transaction { get; }
|
|||
|
|||
#endregion
|
|||
|
|||
#region Transaction
|
|||
|
|||
// /// <summary>启动事务。</summary>
|
|||
// /// <param name="isolation">事务锁定:默认为快照方式,在完成提交前,其它连接无法获取当前事务挂起的更改。</param>
|
|||
// /// <param name="commit">当关闭连接时,提交或回滚未处理的事务。</param>
|
|||
// /// <remarks>当存在已经启动的事务时,无法再次启动(返回 NULL 值)。</remarks>
|
|||
// string Begin(IsolationLevel isolation = IsolationLevel.Snapshot, bool commit = true);
|
|||
|
|||
/// <summary>启动事务。</summary>
|
|||
/// <param name="commit">当关闭连接时,提交或回滚未处理的事务。</param>
|
|||
/// <remarks>当存在已经启动的事务时,无法再次启动(返回 NULL 值)。</remarks>
|
|||
string Begin(bool commit = true); |
|||
|
|||
/// <summary>提交事务。</summary>
|
|||
/// <remarks>异常常见于事务已经提交或连接已断开。</remarks>
|
|||
/// <returns>提交失败时返回错误信息,成功时返回 NULL 值。</returns>
|
|||
string Commit(); |
|||
|
|||
/// <summary>从挂起状态回滚事务。</summary>
|
|||
/// <remarks>异常常见于事务已经提交、已回滚或连接已断开。</remarks>
|
|||
/// <returns>提交失败时返回错误信息,成功时返回 NULL 值。</returns>
|
|||
string Rollback(); |
|||
|
|||
#endregion
|
|||
|
|||
} |
|||
|
|||
} |
@ -1,17 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.Source |
|||
{ |
|||
|
|||
/// <summary>数据库引擎接口。</summary>
|
|||
public interface IDbClientBase : IDisposable |
|||
{ |
|||
|
|||
/// <summary>获取或设置日志记录器。</summary>
|
|||
Logger Logger { get; set; } |
|||
|
|||
} |
|||
|
|||
} |
@ -1,45 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Apewer |
|||
{ |
|||
|
|||
/// <summary>系统实用工具。</summary>
|
|||
public class SystemUtility |
|||
{ |
|||
|
|||
#if NETSTD || NETCORE
|
|||
|
|||
/// <summary>当前操作系统是 Windows。</summary>
|
|||
public static bool IsWindows { get => System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows); } |
|||
|
|||
/// <summary>当前操作系统是 OS X 或 macOS。</summary>
|
|||
public static bool IsOSX { get => System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX); } |
|||
|
|||
/// <summary>当前操作系统是 Linux。</summary>
|
|||
public static bool IsLinux { get => System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux); } |
|||
|
|||
#endif
|
|||
|
|||
/// <summary></summary>
|
|||
public static void SetConsoleCtrlCancel(Func<bool> exit) |
|||
{ |
|||
const string postfix = " - 按 CTRL + C 可安全退出"; |
|||
var title = Console.Title; |
|||
if (!title.EndsWith(postfix)) |
|||
{ |
|||
title = title + postfix; |
|||
Console.Title = title; |
|||
} |
|||
|
|||
if (exit == null) return; |
|||
Console.CancelKeyPress += (s, e) => |
|||
{ |
|||
e.Cancel = !exit(); |
|||
}; |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
Loading…
Reference in new issue