| 類型 | 特徵 | 典型用途 | 回傳方式 |
|---|---|---|---|
| 結果集型 | 返回資料列集合 | 查詢、報表、下拉選單資料源 | SqlDataReader / DataSet |
| 標量型 | 返回單一值 | 統計計數、ID 生成、驗證結果 | ExecuteScalar() |
| 行為型 | 無返回值,僅執行動作 | Insert/Update/Delete、批次處理 | ExecuteNonQuery() (影響列數) |
適用於:需輸出參數、返回值、交易控制、型別安全的嚴謹場景。
public int ExecuteWithOutputParams(DateTime startDate, out int totalCount, out decimal avgAmount) {
totalCount = 0; avgAmount = 0;
int returnValue = -1;
using (SqlConnection conn = new SqlConnection(connStr))
using (SqlCommand cmd = new SqlCommand("usp_GetOrderStats", conn)) {
cmd.CommandType = CommandType.StoredProcedure;
// 輸入參數
cmd.Parameters.Add("@StartDate", SqlDbType.DateTime).Value = startDate;
// 輸出參數
SqlParameter pCount = cmd.Parameters.Add("@TotalCount", SqlDbType.Int);
pCount.Direction = ParameterDirection.Output;
SqlParameter pAvg = cmd.Parameters.Add("@AvgAmount", SqlDbType.Decimal);
pAvg.Precision = 18; pAvg.Scale = 2;
pAvg.Direction = ParameterDirection.Output;
// 返回值參數 (必須為第一個參數)
SqlParameter pRet = cmd.Parameters.Add("@ReturnValue", SqlDbType.Int);
pRet.Direction = ParameterDirection.ReturnValue;
conn.Open();
cmd.ExecuteNonQuery(); // 或 ExecuteReader() 若有結果集
// 讀取輸出與返回值
totalCount = (int)pCount.Value;
avgAmount = (decimal)pAvg.Value;
returnValue = (int)pRet.Value;
}
return returnValue;
}
Direction = ReturnValue。SqlDbType.Decimal 時,必須設定 Precision/Scale,否則預設 (18,0) 導致小數截斷。.Value 會拋例外,需先檢查 Convert.IsDBNull(p.Value)。SqlDataReader.NextResult() 依序讀取。將參數內插進 SQL 字串,如同在 SSMS 執行 EXEC usp_GetOrder @Id=1, @Name='Test'。
public DataTable ExecuteDynamic(string procName, params object[] paramValues) {
// 產生 @p0, @p1... 佔位符
var paramNames = Enumerable.Range(0, paramValues.Length)
.Select(i => "@p" + i)
.ToArray();
string sql = string.Format("EXEC {0} {1}", procName,
string.Join(", ", paramNames.Select((n, i) => n + "=" + FormatValue(paramValues[i]))));
using (SqlConnection conn = new SqlConnection(connStr))
using (SqlDataAdapter da = new SqlDataAdapter(sql, conn)) {
DataTable dt = new DataTable();
da.Fill(dt);
return dt;
}
}
// 簡易值格式化 (僅示範,實務請用參數化防注入)
string FormatValue(object v) {
if (v == null) return "NULL";
if (v is string || v is DateTime) return "'" + v.ToString().Replace("'", "''") + "'";
return v.ToString();
}
| 維度 | 標準參數化 | 動態字串 |
|---|---|---|
| 程式碼行數 | 較多 (每參數 3-5 行) | 極少 (一行調用) |
| 型別安全 | 完整 | 無 (字串轉換) |
| SQL Injection 風險 | 無 | 高 (需自行過濾) |
| 輸出參數/返回值 | 完整支援 | 無法取得 |
| 效能 (計畫快取) | 最佳 (參數化計畫重用) | 差 (每組參數產生新計畫) |
| 除錯便利性 | 需查 Parameters 集合 | 可直接複製 SQL 去 SSMS 執行 |
public static class SqlHelper {
// 1. 執行無回傳值存儲過程 (行為型)
public static int ExecuteNonQuery(string procName, params SqlParameter[] parameters) {
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand(procName, conn)) {
cmd.CommandType = CommandType.StoredProcedure;
if (parameters != null) cmd.Parameters.AddRange(parameters);
conn.Open();
return cmd.ExecuteNonQuery();
}
}
// 2. 執行單一值存儲過程 (標量型)
public static T ExecuteScalar(string procName, params SqlParameter[] parameters) {
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand(procName, conn)) {
cmd.CommandType = CommandType.StoredProcedure;
if (parameters != null) cmd.Parameters.AddRange(parameters);
conn.Open();
object result = cmd.ExecuteScalar();
return result == null || result == DBNull.Value ? default(T) : (T)result;
}
}
// 3. 執行結果集存儲過程 - DataTable
public static DataTable ExecuteDataTable(string procName, params SqlParameter[] parameters) {
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand(procName, conn)) {
cmd.CommandType = CommandType.StoredProcedure;
if (parameters != null) cmd.Parameters.AddRange(parameters);
using (var da = new SqlDataAdapter(cmd)) {
var dt = new DataTable();
da.Fill(dt);
return dt;
}
}
}
// 4. 執行結果集存儲過程 - 強型別列舉 (高效能)
public static IEnumerable ExecuteReader(string procName, Func mapper, params SqlParameter[] parameters) {
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand(procName, conn)) {
cmd.CommandType = CommandType.StoredProcedure;
if (parameters != null) cmd.Parameters.AddRange(parameters);
conn.Open();
using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) {
while (reader.Read()) yield return mapper(reader);
}
}
}
}
// 行為型
SqlHelper.ExecuteNonQuery("usp_DeleteOrder",
new SqlParameter("@OrderId", 1001));
// 標量型
int newId = SqlHelper.ExecuteScalar("usp_InsertOrder",
new SqlParameter("@CustomerId", 50),
new SqlParameter("@Total", 123.45m));
// 結果集 - DataTable
DataTable dt = SqlHelper.ExecuteDataTable("usp_GetOrdersByDate",
new SqlParameter("@StartDate", DateTime.Today.AddDays(-30)));
// 結果集 - 強型別 (零 Boxing、高效能)
var orders = SqlHelper.ExecuteReader("usp_GetOrdersByDate",
r => new Order { Id = r.GetInt32(0), Date = r.GetDateTime(1), Total = r.GetDecimal(2) },
new SqlParameter("@StartDate", DateTime.Today.AddDays(-30)));
SqlBulkCopy 或 Table-Valued Parameters (TVP)。ExecuteNonQueryAsync、ExecuteReaderAsync 避免阻塞執行緒。