You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

655 lines
28 KiB

using System;
using System.Collections.Generic;
using System.Data;
namespace Apewer.Source
{
/// <summary>数据库客户端基类。</summary>
public abstract class DbClient : IDbClient
{
/// <summary></summary>
public virtual Logger Logger { get; set; }
/// <summary></summary>
internal DbClient(Timeout timeout)
{
_timeout = timeout ?? Timeout.Default;
}
#region Connection
string _key = TextUtility.Key();
Timeout _timeout = null;
IDbConnection _conn = null;
bool _disposed = false;
/// <summary></summary>
internal protected object Locker = new object();
/// <summary>此连接的唯一标识。</summary>
public virtual string Key { get => _key; }
/// <summary></summary>
public virtual Timeout Timeout { get => _timeout; set => _timeout = value ?? Timeout.Default; }
/// <summary>获取当前的 SqlConnection 对象。</summary>
public IDbConnection Connection { get => _conn; }
/// <summary>此连接已释放。</summary>
public virtual bool Disposed { get => _disposed; }
/// <summary>连接处于打开状态。</summary>
public virtual bool Online { get => _conn == null ? false : (_conn.State == ConnectionState.Open); }
/// <summary>连接字符串。</summary>
public abstract string ConnectionString { get; }
/// <summary>关闭连接后,执行的方法。</summary>
public virtual Action<IDbAdo> OnClosed { get; set; }
/// <summary>连接数据库,若未连接则尝试连接,获取连接成功的状态。</summary>
public string Connect()
{
if (_disposed)
{
if (ThrowAdoException) throw new ObjectDisposedException(GetType().Name);
return "当前连接已释放,无法再次连接。";
}
if (_conn == null) _conn = GetConnection();
if (_conn.State == ConnectionState.Open) return null;
if (ThrowAdoException)
{
_conn.Open();
if (_conn.State != ConnectionState.Open)
{
var message = $"连接后状态为 {_conn.State},无法验证打开状态。";
Logger.Error(this, "Connect", message);
throw new SqlException(message);
}
return null;
}
try
{
_conn.Open();
if (_conn.State != ConnectionState.Open)
{
var message = $"连接后状态为 {_conn.State},无法验证打开状态。";
Logger.Error(this, "Connect", message);
return "连接失败," + message;
}
return null;
}
catch (Exception ex)
{
Logger.Error(this, "Connect", ex.Message);
if (ThrowAdoException) throw;
return ex.Message;
}
}
/// <summary>更改已打开的数据库。</summary>
public virtual string Change(string store)
{
if (store.IsEmpty())
{
if (ThrowAdoException) throw new ArgumentNullException(nameof(store));
return "未指定数据名称。";
}
var connect = Connect();
if (connect.NotEmpty())
{
if (ThrowAdoException) throw new Exception(connect);
return connect;
}
try
{
Connection.ChangeDatabase(store);
return null;
}
catch (Exception ex)
{
Logger.Error(this, "Change", ex.Message);
if (ThrowAdoException) throw;
return ex.Message();
}
}
/// <summary>关闭连接,并释放对象所占用的系统资源。</summary>
public virtual void Close()
{
if (_disposed) return;
_disposed = true;
if (_conn != null)
{
if (_transaction != null)
{
if (_autocommit) Commit();
else Rollback();
_transaction = null;
}
try { _conn.Close(); } catch { }
var onClosed = OnClosed;
if (onClosed != null) onClosed.Invoke(this);
}
}
/// <summary>关闭连接,释放对象所占用的系统资源,并清除连接信息。</summary>
public virtual void Dispose()
{
Close();
}
#endregion
#region Transaction
const string TransactionNotFound = "事务不存在。";
private IDbTransaction _transaction = null;
private bool _autocommit = false;
/// <summary>获取已启动的事务。</summary>
public virtual IDbTransaction Transaction { get => _transaction; }
string Begin(bool commit, Class<IsolationLevel> isolation)
{
if (_transaction != null)
{
const string msg = "已存在未完成的事务,无法再次启动。";
if (ThrowAdoException) throw new SqlException(msg);
return msg;
}
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());
if (ThrowAdoException) throw;
return ex.Message();
}
}
/// <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>
public virtual string Begin(bool commit = false) => Begin(commit, null);
/// <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 virtual string Begin(bool commit, IsolationLevel isolation) => Begin(commit, new Class<IsolationLevel>(isolation));
/// <summary>提交事务。</summary>
public virtual string Commit()
{
if (_transaction == null)
{
if (ThrowAdoException) throw new SqlException(TransactionNotFound);
return TransactionNotFound;
}
try
{
_transaction.Commit();
RuntimeUtility.Dispose(_transaction);
_transaction = null;
return null;
}
catch (Exception ex)
{
RuntimeUtility.Dispose(_transaction);
_transaction = null;
Logger.Error(this, "Commit", ex.Message());
if (ThrowAdoException) throw;
return ex.Message();
}
}
/// <summary>从挂起状态回滚事务。</summary>
public virtual string Rollback()
{
if (_transaction == null)
{
if (ThrowAdoException) throw new SqlException(TransactionNotFound);
return TransactionNotFound;
}
try
{
_transaction.Rollback();
RuntimeUtility.Dispose(_transaction);
_transaction = null;
return null;
}
catch (Exception ex)
{
RuntimeUtility.Dispose(_transaction);
_transaction = null;
Logger.Error(this, "Rollback", ex.Message());
if (ThrowAdoException) throw;
return ex.Message();
}
}
#endregion
#region ADO
/// <summary>允许 ADO 抛出异常,取代返回错误信息。</summary>
/// <value>FALSE(默认值)</value>
public virtual bool ThrowAdoException { get; set; }
/// <summary>查询。</summary>
/// <param name="sql">SQL 语句。</param>
/// <param name="parameters">为 SQL 语句提供的参数。</param>
public IQuery Query(string sql, IEnumerable<IDataParameter> parameters = null)
{
if (TextUtility.IsEmpty(sql)) return new Query(false, "语句无效。");
var connected = Connect();
if (connected.NotEmpty()) return new Query(false, connected);
lock (Locker)
{
try
{
using (var command = _conn.CreateCommand())
{
command.Connection = _conn;
if (_timeout != null) command.CommandTimeout = Timeout.Query;
command.CommandText = sql;
if (_transaction != null) command.Transaction = _transaction;
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 = CreateDataAdapter(command); }
catch (Exception adapterEx) { ex = adapterEx; }
if (ex != null)
{
Logger.Error(this, "Query", ex.Message, sql);
return new Query(ex);
}
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);
Logger.Info(this, "Query", sql);
return new Query(tables, true);
}
else
{
Logger.Error(this, "Query", "查询结果不包含任何数据表。", sql);
return new Query(false, "查询结果不包含任何数据表。");
}
}
}
}
catch (Exception exception)
{
Logger.Error(this, "Query", exception.Message, sql);
if (ThrowAdoException) throw exception;
return new Query(exception);
}
}
}
/// <summary>输出查询结果的首列数据。</summary>
/// <exception cref="SqlException"></exception>
protected string[] QueryStrings(string sql, string[] excluded = null)
{
if (Connect().NotEmpty()) return new string[0];
using (var query = Query(sql))
{
if (!query.Success) throw new SqlException(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();
}
}
/// <summary>执行。</summary>
/// <param name="sql">SQL 语句。</param>
/// <param name="parameters">为 SQL 语句提供的参数。</param>
/// <param name="autoTransaction">自动启动事务。</param>
public IExecute Execute(string sql, IEnumerable<IDataParameter> parameters = null, bool autoTransaction = false)
{
if (TextUtility.IsEmpty(sql)) return new Execute(false, "语句无效。");
lock (Locker)
{
var connected = Connect();
if (connected.NotEmpty()) return new Execute(false, connected);
var tempTran = _transaction == null && autoTransaction;
if (tempTran) Begin();
try
{
using (var command = _conn.CreateCommand())
{
command.Connection = _conn;
if (_transaction != null) command.Transaction = _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 (tempTran) Commit(); // todo 此处应该检查事务提交产生的错误。
Logger.Info(this, "Execute", sql);
return new Execute(true, rows);
}
}
catch (Exception exception)
{
Logger.Error(this, "Execute", exception.Message, sql);
if (tempTran) Rollback();
if (ThrowAdoException) throw exception;
return new Execute(exception);
}
}
}
/// <summary>查询数据库中的所有表名。</summary>
public abstract string[] TableNames();
/// <summary>查询数据库实例中的所有数据库名。</summary>
public abstract string[] StoreNames();
/// <summary>查询表中的所有列名。</summary>
public abstract string[] ColumnNames(string tableName);
#endregion
#region Parameter
/// <summary>创建参数。</summary>
/// <exception cref="ArgumentNullException"></exception>
public IDataParameter Parameter(string name, object value)
{
if (name.IsEmpty()) throw new ArgumentNullException(nameof(name));
var p = CreateParameter();
p.ParameterName = name;
p.Value = value ?? DBNull.Value;
return p;
}
/// <summary>创建参数。</summary>
/// <exception cref="ArgumentNullException"></exception>
public IDataParameter Parameter(string name, object value, DbType type)
{
if (name.IsEmpty()) throw new ArgumentNullException(nameof(name));
var p = CreateParameter();
p.ParameterName = name;
p.Value = value ?? DBNull.Value;
p.DbType = type;
return p;
}
#endregion
#region ORM
/// <summary>检查数据模型结构,存在异常时抛出异常。</summary>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ModelException"></exception>
protected static TableStructure Parse(Type model)
{
if (model == null) throw new ArgumentNullException(nameof(model), "数据模型类型无效。");
var ts = TableStructure.Parse(model);
if (ts == null) throw ModelException.InvalidStructure(model);
if (ts.TableName.IsEmpty()) throw ModelException.InvalidTableName(ts.Model);
if (ts.Key == null || ts.Key.Field.IsEmpty()) throw ModelException.MissingKey(ts.Model);
if (ts.Flag == null || ts.Flag.Field.IsEmpty()) throw ModelException.MissingFlag(ts.Model);
return ts;
}
/// <summary>初始化指定类型,以创建表或增加字段。</summary>
/// <param name="table">指定新的表名。</param>
/// <returns>错误信息。当成功时候返回空字符串。</returns>
public string Initialize<T>(string table = null) where T : class, new() => Initialize(typeof(T), table);
/// <summary>初始化指定类型,以创建表或增加字段。</summary>
/// <param name="model">要初始化的类型。</param>
/// <param name="table">指定新的表名。</param>
/// <returns>错误信息。当成功时候返回空字符串。</returns>
public string Initialize(Type model, string table = null)
{
if (model == null) return "参数 model 无效。";
if (!model.IsClass) return $"类型 <{model.FullName}> 不是类。";
if (model.IsAbstract) return $"类型 <{model.FullName}> 是抽象类。";
if (!RuntimeUtility.CanNew(model)) return $"类型 <{model.FullName}> 无法创建新实例。";
var structure = TableStructure.Parse(model);
if (structure == null) return "无法解析记录模型。";
// 连接数据库。
var connect = Connect();
if (connect.NotEmpty()) return connect;
return Initialize(structure, table);
}
/// <summary></summary>
protected abstract string Initialize(TableStructure structure, string table = null);
/// <summary>插入记录。</summary>
/// <param name="record">要插入的记录实体。</param>
/// <param name="table">插入到指定表。当不指定时,由 record 类型决定。</param>
/// <param name="adjust">调整数据模型,补充缺少的属性。</param>
/// <returns>错误信息。当成功时候返回空字符串。</returns>
public abstract string Insert(object record, string table = null, bool adjust = true);
/// <summary>使用指定语句查询,获取查询结果。</summary>
/// <param name="model">目标记录的类型。</param>
/// <param name="sql">要执行的 SQL 语句。</param>
/// <param name="parameters">为 SQL 语句提供的参数。</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ModelException"></exception>
/// <exception cref="SqlException"></exception>
public object[] Query(Type model, string sql, IEnumerable<IDataParameter> parameters = null)
{
if (string.IsNullOrEmpty(sql)) throw new ArgumentNullException(nameof(sql), "SQL 语句无效。");
using (var query = Query(sql, parameters))
{
if (!query.Success) throw new SqlException(query, sql);
return SourceUtility.Fill(query, model);
}
}
/// <summary>使用指定语句查询,获取查询结果。</summary>
/// <param name="sql">要执行的 SQL 语句。</param>
/// <param name="parameters">为 SQL 语句提供的参数。</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ModelException"></exception>
/// <exception cref="SqlException"></exception>
public T[] Query<T>(string sql, IEnumerable<IDataParameter> parameters = null) where T : class, new() => Query(typeof(T), sql, parameters).As<object, T>();
#endregion
#region ORM: Record
/// <summary>更新记录。</summary>
/// <param name="record">要更新的记录实体。</param>
/// <param name="table">更新指定的表。当不指定时,由 record 类型决定。</param>
/// <param name="adjust">调整数据模型,补充缺少的属性。</param>
/// <returns>错误信息。当成功时候返回空字符串。</returns>
public abstract string Update(IRecord record, string table = null, bool adjust = true);
/// <summary>生成用于 Keys 方法的 SQL 语句。</summary>
protected abstract string Keys(string tableName, string keyField, string flagField, long flagValue);
/// <summary>获取指定类型的主键,按 Flag 属性筛选。</summary>
/// <param name="model">要查询的类型。</param>
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ModelException"></exception>
/// <exception cref="SqlException"></exception>
public string[] Keys(Type model, long flag = 0)
{
var ts = Parse(model);
var sql = Keys(ts.TableName, ts.Key.Field, ts.Flag.Field, flag);
return QueryStrings(sql);
}
/// <summary>获取指定类型的主键,按 Flag 属性筛选。</summary>
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
/// <exception cref="ModelException"></exception>
/// <exception cref="SqlException"></exception>
public string[] Keys<T>(long flag = 0) where T : class, IRecord, new() => Keys(typeof(T), flag);
/// <summary>生成用于 Get 方法的 SQL 语句。</summary>
protected abstract string Get(string tableName, string keyField, string keyValue, string flagField, long flagValue);
/// <summary>获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。</summary>
/// <param name="model">目标记录的类型。</param>
/// <param name="key">目标记录的主键。</param>
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ModelException"></exception>
/// <exception cref="SqlException"></exception>
public object Get(Type model, string key, long flag = 0)
{
if (model == null) throw new ArgumentNullException(nameof(model));
var ts = TableStructure.Parse(model);
if (ts == null) throw new ModelException($"无法解析类型 {model.Name}。", model);
key = key.SafeKey();
if (key.IsEmpty()) return null;
var sql = Get(ts.TableName, ts.Key.Field, key, ts.Flag.Field, flag);
var records = Query(model, sql, null);
return records.First();
}
/// <summary>获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。</summary>
/// <param name="key">目标记录的主键。</param>
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
/// <exception cref="ModelException"></exception>
/// <exception cref="SqlException"></exception>
public T Get<T>(string key, long flag = 0) where T : class, IRecord, new() => Get(typeof(T), key, flag) as T;
/// <summary>生成用于 List 方法的 SQL 语句。</summary>
protected abstract string List(string tableName, string flagField, long flagValue);
/// <summary>查询所有记录,可按 Flag 筛选。</summary>
/// <param name="model">目标记录的类型。</param>
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ModelException"></exception>
/// <exception cref="SqlException"></exception>
public object[] List(Type model, long flag = 0)
{
if (model == null) throw new ArgumentNullException(nameof(model));
var ts = Parse(model);
if (ts == null) throw new ModelException($"无法解析类型 {model.Name}。", model);
var sql = List(ts.TableName, ts.Flag.Field, flag);
return Query(model, sql, null);
}
/// <summary>查询所有记录,可按 Flag 筛选。</summary>
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
/// <exception cref="ModelException"></exception>
/// <exception cref="SqlException"></exception>
public T[] List<T>(long flag = 0) where T : class, IRecord, new() => List(typeof(T), flag).As<object, T>();
#endregion
#region Static
/// <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.TableName;
}
#endregion
#region Derived
/// <summary>为 Ado 创建 IDataAdapter 对象。</summary>
protected abstract IDataAdapter CreateDataAdapter(IDbCommand command);
/// <summary>为 Ado 创建 IDbConnection 对象。</summary>
protected abstract IDbConnection GetConnection();
/// <summary>为 Ado 创建 IDataParameter 对象。</summary>
protected abstract IDataParameter CreateParameter();
#endregion
}
}