IDataReader 提供唯讀、前向的資料流存取,效能優於 DataSet(無需載入整個資料集到記憶體)。但在連線中斷、例外拋出、資料繫結失敗等情境下,若未正確關閉 SqlConnection,連線池會被佔用,導致後續請求無法取得連線(預設連線池上限約 100,Web 應用更低)。
// 危險:若 DataBind 拋出例外,reader 與 connection 皆未關閉
using (var conn = new SqlConnection(connStr)) {
var cmd = new SqlCommand(sql, conn);
conn.Open();
var reader = cmd.ExecuteReader();
grid.DataSource = reader;
grid.DataBind(); // 可能拋出例外
reader.Close(); // 不會執行到
}
將「使用 Reader 的邏輯」封裝為委派,由資料存取元件負責建立、開啟、關閉連線與 Reader,確保 using 區塊涵蓋整個生命週期。
public static class DbHelper {
public delegate void ReaderHandler(IDataReader reader);
public static void ExecuteReader(string sql, ReaderHandler handler, params SqlParameter[] parameters) {
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand(sql, conn)) {
cmd.Parameters.AddRange(parameters);
conn.Open();
// CommandBehavior.CloseConnection:關閉 Reader 時自動關閉 Connection
using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) {
handler(reader); // 呼叫端處理資料繫結、讀取等邏輯
}
}
}
}
// 使用端
DbHelper.ExecuteReader("SELECT * FROM Users WHERE Id=@id", reader => {
grid.DataSource = reader;
grid.DataBind();
}, new SqlParameter("@id", userId));
SqlConnection、SqlCommand、SqlDataReader 皆正確 Disposeusing 的 finally 區塊釋放資源
public static async Task ExecuteReaderAsync(string sql, Func handler, params SqlParameter[] parameters) {
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand(sql, conn)) {
cmd.Parameters.AddRange(parameters);
await conn.OpenAsync();
using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)) {
await handler(reader);
}
}
}
委派模式將資源擁有權集中在資料存取層,徹底解決 IDataReader 洩漏問題,同時保持呼叫端彈性。現代開發建議搭配 Dapper、EF Core 等 ORM,進一步降低手寫 ADO.NET 程式碼風險。