using System.Collections;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using Sim.Data.Engine;
namespace System.Data.SqlClient;
/// 执行 T-SQL 语句。一条命令里可以有多条语句(用分号或换行分开)
public sealed class SqlCommand : DbCommand, ICloneable
{
private SqlConnection? _connection;
private string _commandText = "";
private CommandType _commandType = CommandType.Text;
public SqlCommand() { }
public SqlCommand(string cmdText) => CommandText = cmdText;
public SqlCommand(string cmdText, SqlConnection connection) : this(cmdText) => Connection = connection;
public SqlCommand(string cmdText, SqlConnection connection, SqlTransaction transaction) : this(cmdText, connection) => Transaction = transaction;
[Diagnostics.CodeAnalysis.AllowNull]
public override string CommandText
{
get => _commandText;
set => _commandText = value ?? "";
}
public override int CommandTimeout { get; set; } = 30;
public override CommandType CommandType
{
get => _commandType;
set => _commandType = value;
}
public new SqlConnection? Connection
{
get => _connection;
set => _connection = value;
}
protected override DbConnection? DbConnection
{
get => _connection;
set => _connection = (SqlConnection?)value;
}
public new SqlParameterCollection Parameters { get; } = new();
protected override DbParameterCollection DbParameterCollection => Parameters;
public new SqlTransaction? Transaction { get; set; }
protected override DbTransaction? DbTransaction
{
get => Transaction;
set => Transaction = (SqlTransaction?)value;
}
public override bool DesignTimeVisible { get; set; } = true;
public override UpdateRowSource UpdatedRowSource { get; set; } = UpdateRowSource.Both;
public override int ExecuteNonQuery() => Run(nameof(ExecuteNonQuery)).RecordsAffected;
public override object? ExecuteScalar()
{
var result = Run(nameof(ExecuteScalar));
return result.Results.Count > 0 && result.Results[0].Rows.Count > 0 ? result.Results[0].Rows[0][0] : null;
}
public new SqlDataReader ExecuteReader() => ExecuteReader(CommandBehavior.Default);
public new SqlDataReader ExecuteReader(CommandBehavior behavior) =>
new(Run(nameof(ExecuteReader)), behavior.HasFlag(CommandBehavior.CloseConnection) ? _connection : null);
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => ExecuteReader(behavior);
public override void Cancel() { }
public override void Prepare() { }
public new SqlParameter CreateParameter() => new();
protected override DbParameter CreateDbParameter() => CreateParameter();
public SqlCommand Clone()
{
var clone = new SqlCommand(_commandText, _connection!) { CommandType = _commandType, CommandTimeout = CommandTimeout, Transaction = Transaction };
foreach (SqlParameter parameter in Parameters) clone.Parameters.Add(parameter.Clone());
return clone;
}
object ICloneable.Clone() => Clone();
private BatchResult Run(string operation)
{
if (_connection == null) throw new InvalidOperationException($"{operation}: Connection 属性尚未初始化。");
var session = _connection.Session(operation);
if (string.IsNullOrWhiteSpace(_commandText)) throw new InvalidOperationException($"{operation}: CommandText 属性尚未初始化");
if (_commandType == CommandType.StoredProcedure) throw SqlException.NotSupported("存储过程");
try
{
return session.Execute(_commandText, name =>
{
var parameter = Parameters.Find(name);
return parameter == null ? (false, null) : (true, parameter.Value);
});
}
catch (SqlException ex)
{
throw ex.Rethrow();
}
}
}
public sealed class SqlParameter : DbParameter, ICloneable
{
private SqlDbType _sqlDbType = SqlDbType.NVarChar;
private bool _typeSet;
private string _parameterName = "";
private string _sourceColumn = "";
public SqlParameter() { }
public SqlParameter(string parameterName, object? value)
{
ParameterName = parameterName;
Value = value;
}
public SqlParameter(string parameterName, SqlDbType dbType)
{
ParameterName = parameterName;
SqlDbType = dbType;
}
public SqlParameter(string parameterName, SqlDbType dbType, int size) : this(parameterName, dbType) => Size = size;
public SqlParameter(string parameterName, SqlDbType dbType, int size, string sourceColumn) : this(parameterName, dbType, size) => SourceColumn = sourceColumn;
[Diagnostics.CodeAnalysis.AllowNull]
public override string ParameterName
{
get => _parameterName;
set => _parameterName = value ?? "";
}
public override object? Value { get; set; }
public SqlDbType SqlDbType
{
get => _sqlDbType;
set
{
_sqlDbType = value;
_typeSet = true;
}
}
public override DbType DbType
{
get => _sqlDbType switch
{
SqlDbType.Int => DbType.Int32,
SqlDbType.BigInt => DbType.Int64,
SqlDbType.SmallInt => DbType.Int16,
SqlDbType.TinyInt => DbType.Byte,
SqlDbType.Bit => DbType.Boolean,
SqlDbType.Float => DbType.Double,
SqlDbType.Real => DbType.Single,
SqlDbType.Decimal or SqlDbType.Money => DbType.Decimal,
SqlDbType.DateTime or SqlDbType.SmallDateTime or SqlDbType.Date => DbType.DateTime,
SqlDbType.VarBinary or SqlDbType.Image or SqlDbType.Binary => DbType.Binary,
SqlDbType.UniqueIdentifier => DbType.Guid,
SqlDbType.VarChar or SqlDbType.Char or SqlDbType.Text => DbType.AnsiString,
_ => DbType.String,
};
set => SqlDbType = value switch
{
DbType.Int32 => SqlDbType.Int,
DbType.Int64 => SqlDbType.BigInt,
DbType.Int16 => SqlDbType.SmallInt,
DbType.Byte => SqlDbType.TinyInt,
DbType.Boolean => SqlDbType.Bit,
DbType.Double => SqlDbType.Float,
DbType.Single => SqlDbType.Real,
DbType.Decimal or DbType.Currency => SqlDbType.Decimal,
DbType.DateTime or DbType.Date => SqlDbType.DateTime,
DbType.Binary => SqlDbType.VarBinary,
DbType.Guid => SqlDbType.UniqueIdentifier,
DbType.AnsiString => SqlDbType.VarChar,
_ => SqlDbType.NVarChar,
};
}
public override ParameterDirection Direction { get; set; } = ParameterDirection.Input;
public override bool IsNullable { get; set; }
public override int Size { get; set; }
public new byte Precision { get; set; }
public new byte Scale { get; set; }
[Diagnostics.CodeAnalysis.AllowNull]
public override string SourceColumn
{
get => _sourceColumn;
set => _sourceColumn = value ?? "";
}
public override bool SourceColumnNullMapping { get; set; }
public override DataRowVersion SourceVersion { get; set; } = DataRowVersion.Current;
public override void ResetDbType()
{
_sqlDbType = SqlDbType.NVarChar;
_typeSet = false;
}
internal bool TypeSet => _typeSet;
public SqlParameter Clone() => new(_parameterName, Value)
{
_sqlDbType = _sqlDbType,
_typeSet = _typeSet,
Direction = Direction,
Size = Size,
SourceColumn = SourceColumn,
SourceVersion = SourceVersion,
SourceColumnNullMapping = SourceColumnNullMapping,
IsNullable = IsNullable,
};
object ICloneable.Clone() => Clone();
public override string ToString() => _parameterName;
}
public sealed class SqlParameterCollection : DbParameterCollection
{
private readonly List _items = new();
public override int Count => _items.Count;
public override object SyncRoot => this;
public new SqlParameter this[int index]
{
get => _items[index];
set => _items[index] = value;
}
public new SqlParameter this[string parameterName]
{
get => Find(parameterName) ?? throw new IndexOutOfRangeException($"此 SqlParameterCollection 中未包含带有 ParameterName“{parameterName}”的 SqlParameter。");
set => _items[IndexOf(parameterName)] = value;
}
/// 按名字找参数,"@id" 和 "id" 视为同一个(和 SqlClient 一样不区分大小写)
internal SqlParameter? Find(string name)
{
var bare = name.TrimStart('@');
return _items.FirstOrDefault(p => string.Equals(p.ParameterName.TrimStart('@'), bare, StringComparison.OrdinalIgnoreCase));
}
public SqlParameter Add(SqlParameter value)
{
_items.Add(value ?? throw new ArgumentNullException(nameof(value)));
return value;
}
public override int Add(object value)
{
Add(value as SqlParameter ?? throw new InvalidCastException("SqlParameterCollection 只接受非空的 SqlParameter 类型对象。"));
return _items.Count - 1;
}
public SqlParameter Add(string parameterName, SqlDbType sqlDbType) => Add(new SqlParameter(parameterName, sqlDbType));
public SqlParameter Add(string parameterName, SqlDbType sqlDbType, int size) => Add(new SqlParameter(parameterName, sqlDbType, size));
public SqlParameter Add(string parameterName, SqlDbType sqlDbType, int size, string sourceColumn) => Add(new SqlParameter(parameterName, sqlDbType, size, sourceColumn));
public SqlParameter AddWithValue(string parameterName, object? value) => Add(new SqlParameter(parameterName, value));
public override void AddRange(Array values)
{
foreach (var value in values) Add(value);
}
public void AddRange(SqlParameter[] values) => AddRange((Array)values);
public override void Clear() => _items.Clear();
public override bool Contains(object value) => value is SqlParameter p && _items.Contains(p);
public override bool Contains(string value) => Find(value) != null;
public override void CopyTo(Array array, int index) => ((ICollection)_items).CopyTo(array, index);
public override IEnumerator GetEnumerator() => _items.GetEnumerator();
public override int IndexOf(object value) => value is SqlParameter p ? _items.IndexOf(p) : -1;
public override int IndexOf(string parameterName) => Find(parameterName) is { } p ? _items.IndexOf(p) : -1;
public override void Insert(int index, object value) => _items.Insert(index, (SqlParameter)value);
public override void Remove(object value) => _items.Remove((SqlParameter)value);
public override void RemoveAt(int index) => _items.RemoveAt(index);
public override void RemoveAt(string parameterName) => _items.RemoveAt(IndexOf(parameterName));
protected override DbParameter GetParameter(int index) => _items[index];
protected override DbParameter GetParameter(string parameterName) => this[parameterName];
protected override void SetParameter(int index, DbParameter value) => _items[index] = (SqlParameter)value;
protected override void SetParameter(string parameterName, DbParameter value) => this[parameterName] = (SqlParameter)value;
}