using System.Collections;
using System.Data.Common;
using System.Globalization;
using Sim.Data.Engine;
namespace System.Data.SqlClient;
/// 按行读取查询结果(结果已在内存里,读的时候不再访问数据库)
public sealed class SqlDataReader : DbDataReader
{
private readonly BatchResult _batch;
private readonly SqlConnection? _closeConnection;
private int _resultIndex;
private int _row = -1;
private bool _closed;
internal SqlDataReader(BatchResult batch, SqlConnection? closeConnection)
{
_batch = batch;
_closeConnection = closeConnection;
}
private QueryResult? Current => _resultIndex < _batch.Results.Count ? _batch.Results[_resultIndex] : null;
private object[] Row
{
get
{
if (_closed) throw new InvalidOperationException("阅读器关闭时尝试调用 Read 无效。");
var current = Current;
if (current == null || _row < 0 || _row >= current.Rows.Count) throw new InvalidOperationException("不存在数据时进行无效的读取尝试。");
return current.Rows[_row];
}
}
public override int FieldCount => Current?.Columns.Count ?? 0;
public override bool HasRows => Current?.Rows.Count > 0;
public override bool IsClosed => _closed;
public override int RecordsAffected => _batch.RecordsAffected;
public override int Depth => 0;
public override object this[int ordinal] => GetValue(ordinal);
public override object this[string name] => GetValue(GetOrdinal(name));
public override bool Read()
{
if (_closed) throw new InvalidOperationException("阅读器关闭时尝试调用 Read 无效。");
var current = Current;
if (current == null) return false;
_row++;
return _row < current.Rows.Count;
}
public override bool NextResult()
{
_resultIndex++;
_row = -1;
return Current != null;
}
public override void Close()
{
if (_closed) return;
_closed = true;
_closeConnection?.Close();
}
public override string GetName(int ordinal) => Column(ordinal).Name;
public override int GetOrdinal(string name)
{
var columns = Current?.Columns ?? throw new IndexOutOfRangeException(name);
for (var i = 0; i < columns.Count; i++)
if (string.Equals(columns[i].Name, name, StringComparison.OrdinalIgnoreCase)) return i;
throw new IndexOutOfRangeException(name);
}
public override Type GetFieldType(int ordinal) => Column(ordinal).ClrType;
public override string GetDataTypeName(int ordinal) => Column(ordinal).Type?.Name ?? "sql_variant";
public override object GetValue(int ordinal)
{
Column(ordinal);
return Row[ordinal];
}
public override int GetValues(object[] values)
{
var row = Row;
var count = Math.Min(values.Length, row.Length);
Array.Copy(row, values, count);
return count;
}
public override bool IsDBNull(int ordinal) => GetValue(ordinal) is DBNull;
public override bool GetBoolean(int ordinal) => Cast(ordinal);
public override byte GetByte(int ordinal) => Cast(ordinal);
public override char GetChar(int ordinal) => throw new NotSupportedException("SqlDataReader 不支持 GetChar。");
public override DateTime GetDateTime(int ordinal) => Cast(ordinal);
public override decimal GetDecimal(int ordinal) => Cast(ordinal);
public override double GetDouble(int ordinal) => Cast(ordinal);
public override float GetFloat(int ordinal) => Cast(ordinal);
public override Guid GetGuid(int ordinal) => Cast(ordinal);
public override short GetInt16(int ordinal) => Cast(ordinal);
public override int GetInt32(int ordinal) => Cast(ordinal);
public override long GetInt64(int ordinal) => Cast(ordinal);
public override string GetString(int ordinal) => Cast(ordinal);
/// 和 SqlClient 一样严格:列是 varchar 时 GetInt32 报"指定的转换无效",是 NULL 时报"数据为空"
private T Cast(int ordinal)
{
var value = GetValue(ordinal);
if (value is DBNull) throw new InvalidCastException("数据为空。不能对空值调用此方法或属性。");
if (value is T typed) return typed;
// 表达式列(如 count(*))没有声明类型,数字之间可以互相转换
if (Column(ordinal).Type == null && value is IConvertible && typeof(T) != typeof(string))
return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture);
throw new InvalidCastException("指定的转换无效。");
}
public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length)
{
var bytes = Cast(ordinal);
if (buffer == null) return bytes.Length;
var count = (int)Math.Min(length, bytes.Length - dataOffset);
if (count <= 0) return 0;
Array.Copy(bytes, dataOffset, buffer, bufferOffset, count);
return count;
}
public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length)
{
var text = Cast(ordinal);
if (buffer == null) return text.Length;
var count = (int)Math.Min(length, text.Length - dataOffset);
if (count <= 0) return 0;
text.CopyTo((int)dataOffset, buffer, bufferOffset, count);
return count;
}
public override IEnumerator GetEnumerator() => new DbEnumerator(this, closeReader: false);
/// 列的结构信息:DbDataAdapter 填充、SqlCommandBuilder 生成增删改语句时要用(主键、自增、来自哪张表的哪一列)
public override DataTable GetSchemaTable()
{
var table = new DataTable("SchemaTable");
table.Columns.Add(SchemaTableColumn.ColumnName, typeof(string));
table.Columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int));
table.Columns.Add(SchemaTableColumn.ColumnSize, typeof(int));
table.Columns.Add(SchemaTableColumn.NumericPrecision, typeof(short));
table.Columns.Add(SchemaTableColumn.NumericScale, typeof(short));
table.Columns.Add(SchemaTableColumn.IsUnique, typeof(bool));
table.Columns.Add(SchemaTableColumn.IsKey, typeof(bool));
table.Columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string));
table.Columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(string));
table.Columns.Add(SchemaTableColumn.BaseColumnName, typeof(string));
table.Columns.Add(SchemaTableColumn.BaseSchemaName, typeof(string));
table.Columns.Add(SchemaTableColumn.BaseTableName, typeof(string));
table.Columns.Add(SchemaTableColumn.DataType, typeof(Type));
table.Columns.Add(SchemaTableColumn.AllowDBNull, typeof(bool));
table.Columns.Add(SchemaTableColumn.ProviderType, typeof(int));
table.Columns.Add(SchemaTableColumn.IsAliased, typeof(bool));
table.Columns.Add(SchemaTableColumn.IsExpression, typeof(bool));
table.Columns.Add("IsIdentity", typeof(bool));
table.Columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool));
table.Columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(bool));
table.Columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(bool));
table.Columns.Add(SchemaTableColumn.IsLong, typeof(bool));
table.Columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(bool));
table.Columns.Add("DataTypeName", typeof(string));
var columns = Current?.Columns ?? Array.Empty();
for (var i = 0; i < columns.Count; i++)
{
var c = columns[i];
var row = table.NewRow();
row[SchemaTableColumn.ColumnName] = c.Name;
row[SchemaTableColumn.ColumnOrdinal] = i;
row[SchemaTableColumn.ColumnSize] = c.Type?.MaxLength ?? (c.Type?.IsMax == true ? int.MaxValue : 0);
row[SchemaTableColumn.NumericPrecision] = (short)(c.Type?.Precision ?? 0);
row[SchemaTableColumn.NumericScale] = (short)(c.Type?.Scale ?? 0);
row[SchemaTableColumn.IsUnique] = c.IsKey;
row[SchemaTableColumn.IsKey] = c.IsKey;
row[SchemaTableColumn.BaseColumnName] = (object?)c.BaseColumn ?? DBNull.Value;
row[SchemaTableColumn.BaseSchemaName] = c.BaseTable != null ? "dbo" : DBNull.Value;
row[SchemaTableColumn.BaseTableName] = (object?)c.BaseTable ?? DBNull.Value;
row[SchemaTableColumn.DataType] = c.ClrType;
row[SchemaTableColumn.AllowDBNull] = c.AllowNull;
row[SchemaTableColumn.ProviderType] = 0;
row[SchemaTableColumn.IsAliased] = c.BaseColumn != null && !string.Equals(c.BaseColumn, c.Name, StringComparison.OrdinalIgnoreCase);
row[SchemaTableColumn.IsExpression] = c.BaseColumn == null;
row["IsIdentity"] = c.IsIdentity;
row[SchemaTableOptionalColumn.IsAutoIncrement] = c.IsIdentity;
row[SchemaTableOptionalColumn.IsRowVersion] = false;
row[SchemaTableOptionalColumn.IsHidden] = false;
row[SchemaTableColumn.IsLong] = c.Type is { IsMax: true } or { Name: "text" or "ntext" or "image" };
row[SchemaTableOptionalColumn.IsReadOnly] = c.IsIdentity || c.BaseColumn == null;
row["DataTypeName"] = c.Type?.Name ?? "sql_variant";
table.Rows.Add(row);
}
return table;
}
private ResultColumn Column(int ordinal)
{
var columns = Current?.Columns;
if (columns == null || ordinal < 0 || ordinal >= columns.Count) throw new IndexOutOfRangeException("索引超出了数组界限。");
return columns[ordinal];
}
}