將 SqlDataReader (只進流) 轉為 DataTable (內存表),核心步驟:1. 根據 Reader 元數據建立列、2. 循環 Read() 填充行。原代碼邏輯正確,可能因調試視角導致誤以為無數據。
public static DataTable ConvertDataReaderToDataTable(SqlDataReader reader) {
DataTable dt = new DataTable();
// 1. 建立列結構 (Schema)
for (int i = 0; i < reader.FieldCount; i++) {
DataColumn col = new DataColumn();
col.ColumnName = reader.GetName(i);
col.DataType = reader.GetFieldType(i);
dt.Columns.Add(col);
}
// 2. 循環讀取行數據
while (reader.Read()) {
DataRow row = dt.NewRow();
for (int i = 0; i < reader.FieldCount; i++) {
// 直接賦值,DBNull.Value 會自動處理
row[i] = reader[i];
}
dt.Rows.Add(row);
}
// 3. 關閉 Reader (調用者負責,或這裡關閉)
// reader.Close();
return dt;
}
public static DataTable ConvertDataReaderToDataTableOptimized(SqlDataReader reader) {
DataTable dt = new DataTable();
// 使用 GetSchemaTable 獲取完整列元數據 (含主鍵、自增、允許空等)
DataTable schemaTable = reader.GetSchemaTable();
foreach (DataRow schemaRow in schemaTable.Rows) {
DataColumn col = new DataColumn();
col.ColumnName = schemaRow["ColumnName"].ToString();
col.DataType = (Type)schemaRow["DataType"];
col.AllowDBNull = (bool)schemaRow["AllowDBNull"];
col.Unique = (bool)schemaRow["IsUnique"];
col.AutoIncrement = (bool)schemaRow["IsAutoIncrement"];
// 可複製更多屬性...
dt.Columns.Add(col);
}
// 填充數據
while (reader.Read()) {
DataRow row = dt.NewRow();
for (int i = 0; i < reader.FieldCount; i++) {
row[i] = reader[i];
}
dt.Rows.Add(row);
}
return dt;
}
// 最簡潔,內部自動處理 Schema 和數據填充
DataTable dt = new DataTable();
dt.Load(reader); // 自動關閉 reader
return dt;
| 現象 | 可能原因 | 排查 |
|---|---|---|
| Rows.Count = 0 | Reader 已讀完 (Read() 返回 false) | 確保傳入的 Reader 尚未 Read,或調用前未遍歷 |
| Columns 為空 | FieldCount = 0 (SQL 無結果集) | 檢查 SQL 執行是否返回結果 (SELECT vs INSERT/UPDATE) |
| 數據類型不匹配 | GetFieldType 返回 Provider 特定類型 | 用 reader.GetProviderSpecificFieldType(i) 或統一轉 String |
| 大數據量 OOM | DataTable 全量載入內存 | 分頁查詢、流式處理、或用 Dapper 映射實體流 |
// 依賴: Dapper
using (var conn = new SqlConnection(connStr)) {
conn.Open();
using (var reader = cmd.ExecuteReader()) {
// 直接映射到強類型列表,無 DataTable 開銷
var list = reader.Parse().AsList(); // Dapper 拓展方法
// 或手寫映射
var users = new List();
while (reader.Read()) {
users.Add(new User {
Id = reader.GetInt32(0),
Name = reader.GetString(1),
Email = reader.IsDBNull(2) ? null : reader.GetString(2)
});
}
}
}
DataTable.Load(reader):簡潔、官方實現、自動關閉 Reader。using 確保釋放。
4. DBNull 處理:reader.IsDBNull(i) ? null : reader.GetValue(i)。
5. 異步讀取:await reader.ReadAsync()、dt.LoadAsync(reader) (Core 3.0+)。
DataReader → DataTable 是 ADO.NET 經典轉換,理解其「Schema 建立 → 數據填充」兩階段,能靈活應對各種數據源轉換場景。