利用 ADO.NET 接收 SQL Server 預存程序的輸出資料
花個點時間研究後,大致上知道做法,難度雖不高,但有些技巧值得分享,在此提供給大家參考看看。Proc 的輸出可以有多種型態,因此利用 ADO.NET 接收時必須跟著因應,整理如下:
- 資料集 -- 用 SqlDataAdapter 或 SqlDataReader 物件取回,前面說過了,這個跟從資料表或檢視查詢資料作法沒甚麼兩樣。
- 輸出參數 -- 用 SqlCommand 物件逐一加入 SqlParameters 集合物件,所有輸出參數的 ParameterDirection 屬性必須指定為 Output (或 InputOutput) 型態。
- 傳回值 -- 做法如同輸出參數,但在 ParameterDirection 屬性必須指定為 ReturnValue 型態。
- 受影響資料列數 -- 一般做法是用 SqlCommand.ExecuteNonQuery 方法即可取得,但考慮到 Stored Proc 有可能下 SET NOCOUNT ON 陳述式關閉此輸出 (此時 ExecuteNonQuery 方法的回傳值永遠是 -1),因此可以考慮在 Stored Proc 裡宣告足夠的變數去存放每一段 DML 陳述式執行後的 @@ROWCOUNT,再考慮用輸出參數的方式取回 (若是偏好傳回資料集也是可以)。
- 警告或 PRINT 陳述式的輸出 -- 在 SQL Server 中,錯誤嚴重性層級 10 以下的視為警告訊息,且不會擲回例外狀況,還有,有時候為了除錯,我們會利用 PRINT 輸出訊息,這類的訊息可透過 SqlConnection 物件的 InfoMessage 事件處理,詳細可參考:ADO.NET - 使用連接事件。
@CustomerID nchar(5),
@ShipName nvarchar(30),
@ShipAddress nvarchar(50),
@ShipCity nvarchar(15),
@ShipPostalCode varchar(5),
@ShipCountry nvarchar(15),
@IdentityID int = 0 OUTPUT,
@AffectedRows int = 0 OUTPUT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO Orders (
CustomerID,
OrderDate,
RequiredDate,
ShipName,
ShipAddress,
ShipCity,
ShipPostalCode,
ShipCountry
)
VALUES (
@CustomerID,
GETDATE(),
GETDATE(),
@ShipName,
@ShipAddress,
@ShipCity,
@ShipPostalCode,
@ShipCountry
);
SET @AffectedRows = @@ROWCOUNT;
SET @IdentityID = @@IDENTITY;
IF (@IdentityID > 0) PRINT N'新增訂單成功';
ELSE PRINT N'新增訂單失敗';
END
*注意:以上程序為了丟出回傳值、輸出參數、受影響資料列數、訊息,有許多不必要的內容,在實際情況下你很少需要這麼做。
可以撰寫程式碼了,由於要處理 PRINT 訊息,所以先宣告 SqlConnectin 並註冊 InfoMessage 事件處理常式:
SqlConnection cn = new SqlConnection(ConfigurationManager.ConnectionStrings["NorthwindConnectionString1"].ConnectionString);
protected void Page_Load(object sender, EventArgs e)
{
// 註冊事件處裡常式
cn.InfoMessage += new SqlInfoMessageEventHandler(cn_InfoMessage);
}
void cn_InfoMessage(object sender, SqlInfoMessageEventArgs e)
{
for (int i = 0; i < e.Errors.Count; i++)
{
litMsg.Text += String.Format("警告或 PRINT 訊息:{0}。<br />", e.Errors[i].Message);
}
}
我在頁面上配置一個按鈕 (btnInsert) ,點選後會利用剛剛建立的自訂程序 usp_InsertOrder 新增 CustomerID 是 DRACD 的訂單 (為了簡化,除了 OrderDate、RequiredDate 以外,其他資料也都直接指定),然後透過 Northwind 範例資料庫裡原本就有的程序 CustOrdersOrders 查出 DRACD 所有的訂單,並在 GridView1 顯示出來:
{
// 按下按鈕新增訂單,並帶出特定使用者所有訂單
InsertOrder();
SelectOrders();
}
/// <summary>
/// 新增訂單
/// </summary>
private void InsertOrder()
{
try
{
using (cn)
{
using (SqlCommand cmd = new SqlCommand("dbo.usp_InsertOrder", cn))
{
// 設定查詢型態為預存程序
cmd.CommandType = System.Data.CommandType.StoredProcedure;
// 逐一加入輸入參數
cmd.Parameters.Add("CustomerID", System.Data.SqlDbType.NChar, 5).Value = "DRACD";
cmd.Parameters.Add("ShipName", System.Data.SqlDbType.NVarChar, 30).Value = "Hunterpo";
cmd.Parameters.Add("ShipAddress", System.Data.SqlDbType.NVarChar, 50).Value = "NO.12345, Global Village";
cmd.Parameters.Add("ShipCity", System.Data.SqlDbType.NVarChar, 15).Value = "TAIPEI";
cmd.Parameters.Add("ShipPostalCode", System.Data.SqlDbType.VarChar, 5).Value = "54321";
cmd.Parameters.Add("ShipCountry", System.Data.SqlDbType.NVarChar, 15).Value = "TAIWAN, R.O.C.";
// 宣告參數接收傳回值
SqlParameter rc = new SqlParameter("RC", System.Data.SqlDbType.Int);
rc.Direction = System.Data.ParameterDirection.ReturnValue;
cmd.Parameters.Add(rc);
// 宣告並加入輸出參數
SqlParameter identityID = new SqlParameter("IdentityID", System.Data.SqlDbType.Int);
identityID.Direction = System.Data.ParameterDirection.Output;
SqlParameter affectedRows = new SqlParameter("AffectedRows", System.Data.SqlDbType.Int);
affectedRows.Direction = System.Data.ParameterDirection.Output;
cmd.Parameters.Add(identityID);
cmd.Parameters.Add(affectedRows);
cn.Open();
// 受影響記錄筆數,用以凸顯 SET NOCOUNT ON 陳述式的影響
int affectedRecords = cmd.ExecuteNonQuery();
// 丟出最新識別值訊息
ClientScript.RegisterStartupScript(
GetType(),
"Notify",
String.Format("alert('受影響資料列數:{0} (Output 參數), \\r受影響記錄筆數:{1} (DbCommand.ExecuteNonQuery 回傳值), \\rProc 傳回值:{2} (Return Value), \\r最新 Order ID:{3} (Output 參數)。');",
affectedRows.Value,
affectedRecords,
rc.Value,
identityID.Value),
true);
}
}
}
catch (Exception e)
{
Response.Write(String.Format("<span style='color: #ff0000;'>發生例外:{0}。</span>", e.Message));
}
}
/// <summary>
/// 查詢訂單
/// </summary>
private void SelectOrders()
{
using (SqlConnection cn = new SqlConnection(ConfigurationManager.ConnectionStrings["NorthwindConnectionString1"].ConnectionString))
{
using (SqlCommand cmd = new SqlCommand("dbo.CustOrdersOrders", cn))
{
// 設定查詢型態為預存程序
cmd.CommandType = System.Data.CommandType.StoredProcedure;
// 逐一加入輸入參數
cmd.Parameters.Add("CustomerID", System.Data.SqlDbType.NChar, 5).Value = "DRACD";
cn.Open();
using (DataTable dt = new DataTable("Orders"))
{
dt.Load(cmd.ExecuteReader());
if (dt.Rows.Count > 0)
{
GridView1.DataSource = dt;
GridView1.DataBind();
}
}
}
}
}
執行結果如下圖,一次搞定前面五種狀況:
再強調一次,在 usp_InsertOrder 自訂程序裡,有加 SET NOCOUNT ON 陳述式,所以 SqlCommand.ExecuteNonQuery 方法的回傳值永遠是 -1。
完整程式碼範例可以在此下載 (解壓縮後,用 Visual Studio 以開啟網站方式開啟就可以 Run):StoredProcOutput.rar
PS. 話說發文前我都會搜尋一下,怕有人發過相同主題,結果還真的有人發過同主題… (其中一篇還是站內的夥伴寫的 XD),好在詳細看了一下,我這篇有多寫一些些東西,阿不然可能要砍掉重練了…。我把找到的文章一併列出,大家可以互相參考觀摩一下:
參考資料: