在 Visual Basic .NET 中使用存儲(chǔ)過(guò)程發(fā)布日期: 4/1/2004 | 更新日期: 7/6/2004
Billy Hollis 摘要:Billy Hollis 講述了存儲(chǔ)過(guò)程對(duì)于復(fù)雜系統(tǒng)的益處,將存儲(chǔ)過(guò)程提到了超出演示軟件的高度,并提供了一些手頭的示例,以說(shuō)明如何訪問(wèn)存儲(chǔ)過(guò)程以及如何開(kāi)始在應(yīng)用程序中使用這些存儲(chǔ)過(guò)程。 我們這些作者傾向于將軟件分為兩類(lèi):實(shí)際軟件 和演示軟件。 實(shí)際軟件就是那些在現(xiàn)實(shí)中能夠真正運(yùn)作的軟件。 演示軟件則是為了說(shuō)明某些編程概念而編寫(xiě)的軟件。 各種文章和書(shū)籍上所見(jiàn)到的大多數(shù)代碼都是演示軟件。 這些軟件必須比實(shí)際軟件來(lái)得簡(jiǎn)單;否則,讀者將陷入與所說(shuō)明概念毫無(wú)關(guān)系的各種細(xì)節(jié)中。 但有時(shí)演示軟件簡(jiǎn)單得過(guò)了火。 一味追求簡(jiǎn)單就可能忽略在編寫(xiě)實(shí)際軟件時(shí)所必需的一些細(xì)節(jié)。 我最近注意到的這種例子是關(guān)于數(shù)據(jù)訪問(wèn)的。 我所看到的每個(gè)數(shù)據(jù)訪問(wèn)示例基本上都使用 SQL 語(yǔ)句對(duì)關(guān)系數(shù)據(jù)庫(kù)進(jìn)行讀寫(xiě)訪問(wèn),這樣的關(guān)系數(shù)據(jù)庫(kù)如 Microsoft SQL Server?。 然而,在現(xiàn)實(shí)中,這種做法被認(rèn)為是一種錯(cuò)誤的編程習(xí)慣,可能一些小型的、有限的系統(tǒng)除外。 一個(gè)恰當(dāng)構(gòu)造的 n 層應(yīng)用程序使用存儲(chǔ)過(guò)程進(jìn)行數(shù)據(jù)訪問(wèn),而不是使用 SQL 語(yǔ)句。 在概念上,存儲(chǔ)過(guò)程類(lèi)似于程序中的函數(shù)。 存儲(chǔ)過(guò)程獲得輸入?yún)?shù),以黑盒子 形式運(yùn)行,并返回適當(dāng)?shù)男畔ⅰ?與函數(shù)的不同之處在于,存儲(chǔ)過(guò)程由數(shù)據(jù)庫(kù)引擎執(zhí)行,而不是在程序內(nèi)部執(zhí)行的。 這意味著必須利用一種能夠與數(shù)據(jù)庫(kù)接口的技術(shù)從存儲(chǔ)過(guò)程獲取信息或向其中輸入信息。 在 Microsoft Visual Basic? 6.0 中,這個(gè)技術(shù)就是傳統(tǒng)的 ADO。 在 Visual Basic .NET 中,我們則可以通過(guò) ADO.NET 完成。 如同對(duì)很多編程任務(wù)一樣,Visual Basic .NET 使得利用存儲(chǔ)過(guò)程訪問(wèn)數(shù)據(jù)的操作比 Visual Basic 6.0 利用存儲(chǔ)過(guò)程進(jìn)行數(shù)據(jù)訪問(wèn)容易得多。 Visual Basic .NET 提供了一些向?qū)б詭椭瓿纱诉^(guò)程,只要您知道如何避免一些太隨意的語(yǔ)句,即便是利用 ADO.NET 從頭開(kāi)始編寫(xiě)這種邏輯也不是過(guò)分復(fù)雜的事情。 本文包括一些在利用 ADO.NET 對(duì)存儲(chǔ)過(guò)程進(jìn)行操作時(shí)的基本方法,首先介紹了一種只讀操作,然后繼續(xù)講述使用存儲(chǔ)過(guò)程進(jìn)行插入、刪除和更新數(shù)據(jù)。 即便您不能熟練地編寫(xiě)存儲(chǔ)過(guò)程,您也將從本文中獲益匪淺。 大型編程團(tuán)隊(duì)中的很多開(kāi)發(fā)人員都需要能夠使用由別人編寫(xiě)的存儲(chǔ)過(guò)程。 我們的示例之一就是需要將一個(gè)存儲(chǔ)過(guò)程插入到示例數(shù)據(jù)庫(kù)中,但我們將循序漸進(jìn)地講述該項(xiàng)任務(wù)。 簡(jiǎn)要回顧 ADO.NET 對(duì)本文而言,我必須假設(shè)您已經(jīng)具備了有關(guān) ADO.NET 的基礎(chǔ)知識(shí)。如果您尚未使用 ADO.NET 中的 DataAdapters、DataSets 和 Command 對(duì)象完成一些任務(wù),那么您應(yīng)該讀讀有關(guān) ADO.NET 的入門(mén)文章,包括 Rocky 為本專(zhuān)欄撰寫(xiě)的一篇文章,名為 ADO.NET and You。 簡(jiǎn)要回顧一下,DataSets 在 ADO.NET 中用作數(shù)據(jù)容器,在從數(shù)據(jù)庫(kù)斷開(kāi)時(shí)使用。 DataSet 包含一個(gè)或多個(gè) DataTables,其中每個(gè)包含一個(gè)行集。 對(duì)于熟悉傳統(tǒng)的 ADO 的開(kāi)發(fā)人員而言,DataTable大致可看作是一個(gè)斷開(kāi)的記錄集。 DataAdapters 在連接到數(shù)據(jù)庫(kù)時(shí)起作用。 一個(gè) DataAdapter 的任務(wù)或者是利用來(lái)自數(shù)據(jù)庫(kù)的數(shù)據(jù)填充其中一個(gè) DataTables,或者是將 DataTable 中的更改寫(xiě)回?cái)?shù)據(jù)庫(kù)中,或者是這二者都進(jìn)行。 DataAdapters 需要 Command 對(duì)象來(lái)執(zhí)行各種數(shù)據(jù)庫(kù)操作。 Command 對(duì)象中或者包含一條 SQL 語(yǔ)句,或者包含一個(gè)存儲(chǔ)過(guò)程名稱(chēng),用以指定要如何完成數(shù)據(jù)訪問(wèn)。 每個(gè) DataAdapter都有四個(gè)屬性,表明用于四種數(shù)據(jù)訪問(wèn)類(lèi)型中每一種類(lèi)型的命令對(duì)象:
我們可以用圖的形式來(lái)表示這些對(duì)象及其關(guān)系,如圖 1 所示。 圖 1. 用于訪問(wèn)存儲(chǔ)過(guò)程的主要 ADO.NET 類(lèi),及其相互關(guān)系 到目前為止,您所看到的演示軟件示例可能都是將其 Command 對(duì)象配置為利用 SQL 語(yǔ)句進(jìn)行數(shù)據(jù)訪問(wèn)。 實(shí)際上,其中的某些示例很可能還完全繞過(guò)了 Command 對(duì)象的創(chuàng)建,因?yàn)?DataAdapter的其中一個(gè)構(gòu)造函數(shù)允許在后臺(tái)創(chuàng)建用于選擇數(shù)據(jù)的 Command 對(duì)象。 在開(kāi)始使用存儲(chǔ)過(guò)程之前,讓我們演示這樣一個(gè)示例以作為比較。 本文中所有示例都使用 SQL Server 所附帶的 Northwind 示例數(shù)據(jù)庫(kù)。 我們還將使用專(zhuān)門(mén)針對(duì) SQL Server 而創(chuàng)建的 ADO.NET 類(lèi),而不是使用通用的 OLE DB 類(lèi)。 為了便于訪問(wèn)這些 SQL Server 類(lèi),所有示例都需要在應(yīng)用程序的代碼最上面包括以下一行: Imports System.Data.SQLClient 現(xiàn)在,開(kāi)始我們的第一個(gè)示例,即不通過(guò)存儲(chǔ)過(guò)程執(zhí)行數(shù)據(jù)訪問(wèn)。 在此示例中,我們將檢索 Northwind 數(shù)據(jù)庫(kù) Products 表中所有的產(chǎn)品。 創(chuàng)建一個(gè)新的 Windows 應(yīng)用程序,并在所顯示的空白 Form1 上放置一個(gè)按鈕和一個(gè) DataGrid。 將 DataGrid的 Anchor 屬性設(shè)置到全部四條邊,這樣一來(lái),它就會(huì)隨著窗體的擴(kuò)展而擴(kuò)展。 在按鈕的 Click 事件中,加入以下代碼: Dim sConnectionString As String = _ "server=localhost;uid=sa;pwd=;database=Northwind" Dim sSQL As String = "SELECT * FROM Products" Dim daGetProducts As New SqlDataAdapter(sSQL, sConnectionString) Dim dsProducts As New DataSet() daGetProducts.Fill(dsProducts, "Products") DataGrid1.DataSource = dsProducts.Tables("Products") 根據(jù)計(jì)算機(jī)的具體配置,您可能需要更改連接字符串。 然而,只要建立了數(shù)據(jù)庫(kù)連接,代碼的其他部分就應(yīng)該運(yùn)行正常。 此演示軟件顯示了用于填充和使用 DataSet 的最簡(jiǎn)單且可行的方法。 請(qǐng)注意,該代碼并沒(méi)有創(chuàng)建 Connection 對(duì)象或 Command 對(duì)象。 在現(xiàn)實(shí)中,如果沒(méi)有這些對(duì)象,ADO.NET 是不能運(yùn)行的,但這些對(duì)象是在后臺(tái)創(chuàng)建并使用的。 用于實(shí)例化 SqlDataAdapter 的代碼行傳遞進(jìn)來(lái)一個(gè) SQL 字符串(以配置這個(gè)后臺(tái) Command 對(duì)象)和一個(gè)連接字符串(以配置這個(gè)后臺(tái) Connection 對(duì)象)。 我們可以更改此代碼以使用顯式的 Connection 和 Command 對(duì)象,這樣離演示軟件稍微遠(yuǎn)了一些。 在窗體上放置另一個(gè)按鈕,并將以下代碼加入該按鈕的 Click 事件中: Dim sConnectionString As String = _ "server=localhost;uid=sa;pwd=;database=Northwind" Dim sSQL As String = "SELECT * FROM Products" Dim cnNorthwind As New SqlConnection(sConnectionString) Dim cmdProducts As New SqlCommand(sSQL, cnNorthwind) Dim daGetProducts As New SqlDataAdapter(cmdProducts) Dim dsProducts As New DataSet() daGetProducts.Fill(dsProducts, "Products") DataGrid1.DataSource = dsProducts.Tables("Products") 此代碼顯示了 DataAdapters 的更常見(jiàn)的使用方法;即通過(guò)顯式創(chuàng)建 Connection 和 Command 對(duì)象并將這些對(duì)象附加到 DataAdapter。 通過(guò)在實(shí)例化 DataAdapter 時(shí)傳遞到 cmdProducts 中,DataAdapter 的 SelectCommand 得以自動(dòng)設(shè)置。 隨后,DataAdapter 馬上就可用于訪問(wèn)數(shù)據(jù)庫(kù)。 此代碼的結(jié)果與前一示例的結(jié)果完全相同。 但這段代碼較接近于真實(shí)軟件,它的更多方面還是像演示軟件,因?yàn)閿?shù)據(jù)訪問(wèn)是通過(guò) SQL 語(yǔ)句完成的。 利用簡(jiǎn)單的存儲(chǔ)過(guò)程獲取數(shù)據(jù) 如何更改此演示軟件以使用存儲(chǔ)過(guò)程呢? 只需更改其中的兩行。 在窗體上放置另一個(gè)按鈕,并將以下代碼加入該按鈕的 Click 事件中: Dim sConnectionString As String = _ "server=localhost;uid=sa;pwd=;database=Northwind" Dim cnNorthwind As New SqlConnection(sConnectionString) Dim cmdProducts As New _ SqlCommand("Ten Most Expensive Products", cnNorthwind) cmdProducts.CommandType = CommandType.StoredProcedure Dim daGetProducts As New SqlDataAdapter(cmdProducts) Dim dsProducts As New DataSet() daGetProducts.Fill(dsProducts, "Products") DataGrid1.DataSource = dsProducts.Tables("Products") 這段代碼不再使用 SQL 語(yǔ)句,取而代之的是在實(shí)例化 Command 對(duì)象時(shí)將使用存儲(chǔ)過(guò)程名稱(chēng)。 此外,該 Command對(duì)象的 CommandType 屬性必須設(shè)置為 StoredProcedure。 從該行以后,代碼與前一示例完全相同,但返回不同的數(shù)據(jù)。 該存儲(chǔ)過(guò)程會(huì)找出十項(xiàng)最貴的產(chǎn)品,且僅返回每項(xiàng)產(chǎn)品的名稱(chēng)和價(jià)格。 帶輸入?yún)?shù)的存儲(chǔ)過(guò)程 此示例相當(dāng)簡(jiǎn)單,因?yàn)槠浯鎯?chǔ)過(guò)程不需要任何輸入?yún)?shù)。 也就是說(shuō),找出十項(xiàng)最貴產(chǎn)品的操作并不需要任何外部信息。 該存儲(chǔ)過(guò)程不需要任何外部幫助即可完成操作。 然而,大多數(shù)存儲(chǔ)過(guò)程卻的確需要輸入?yún)?shù)以執(zhí)行其功能。 作為下一個(gè)示例,讓我們看看如何向存儲(chǔ)過(guò)程傳遞輸入?yún)?shù)。 我們將利用 Northwind 數(shù)據(jù)庫(kù)中已有的一個(gè)名為 CustOrderHist 的存儲(chǔ)過(guò)程,并使用 CustomerID 獲取相關(guān)客戶(hù)的所有訂單。 在我們一直使用的窗體上創(chuàng)建另一個(gè)按鈕,并在該按鈕的 Click 事件中加入以下代碼: Dim sConnectionString As String = _ "server=localhost;uid=sa;pwd=;database=Northwind" Dim cnNorthwind As New SqlConnection(sConnectionString) Dim cmdOrders As New SqlCommand("CustOrderHist", cnNorthwind) cmdOrders.CommandType = CommandType.StoredProcedure ' Set up parameter for stored procedure Dim prmCustomerID As New SqlParameter() prmCustomerID.ParameterName = "@CustomerID" prmCustomerID.SqlDbType = SqlDbType.VarChar prmCustomerID.Size = 5 prmCustomerID.Value = "ALFKI" cmdOrders.Parameters.Add(prmCustomerID) Dim daGetOrders As New SqlDataAdapter(cmdOrders) Dim dsOrders As New DataSet() daGetOrders.Fill(dsOrders, "Orders") DataGrid1.DataSource = dsOrders.Tables("Orders") 這段代碼看起來(lái)很像前一示例中的代碼,不同之處在于,在創(chuàng)建 Command 對(duì)象之后,為該對(duì)象配置了一個(gè) Parameter對(duì)象并將其添加到了該命令的參數(shù)集合中。 在此示例中,我們硬編碼了一個(gè)客戶(hù) ID(更接近于演示軟件),而且在通常情況下參數(shù)的 Value屬性應(yīng)該被設(shè)置成某些用戶(hù)輸入數(shù)據(jù)。 然而,可完全按此示例中所示的方式設(shè)定該參數(shù)的其他屬性。 此示例顯式地設(shè)置了所有參數(shù)。 有些開(kāi)發(fā)人員喜歡這種風(fēng)格,且這種風(fēng)格適用于指導(dǎo)目的。 然而,有些開(kāi)發(fā)人員更喜歡另一種等效的形式,這種形式的代碼行更少: Dim sConnectionString As String = _ "server=localhost;uid=sa;pwd=;database=Northwind" Dim cnNorthwind As New SqlConnection(sConnectionString) Dim cmdOrders As New SqlCommand("CustOrderHist", cnNorthwind) cmdOrders.CommandType = CommandType.StoredProcedure cmdOrders.Parameters.Add(New _ SqlParameter("@CustomerID", SqlDbType.VarChar, 5)) cmdOrders.Parameters("@CustomerID").Value = "ALFKI" Dim daGetOrders As New SqlDataAdapter(cmdOrders) Dim dsOrders As New DataSet() daGetOrders.Fill(dsOrders, "Orders") DataGrid1.DataSource = dsOrders.Tables("Orders") 這段代碼的功能與前一示例的功能完全相同。 然而,對(duì)每個(gè)參數(shù),它只需兩行代碼,而不是六行。 如果一個(gè)存儲(chǔ)過(guò)程具有很多參數(shù)(稍后的一些示例就會(huì)有很多參數(shù)),這樣一來(lái)所需的代碼行數(shù)可能會(huì)產(chǎn)生很大的區(qū)別,所以從此往后,我們將使用這種形式。 使用存儲(chǔ)過(guò)程更新數(shù)據(jù)庫(kù) 以上的幾個(gè)示例使用了存儲(chǔ)過(guò)程從數(shù)據(jù)庫(kù)中獲取信息。 在一些復(fù)雜的應(yīng)用程序中,也經(jīng)常使用存儲(chǔ)過(guò)程來(lái)更新、插入和刪除記錄。 讓我們看看如何利用 ADO.NET 進(jìn)行這些操作。 作為第一個(gè)示例,我們將允許 Visual Studio? .NET 中的向?qū)槲覀兙帉?xiě)一組存儲(chǔ)過(guò)程,并創(chuàng)建相應(yīng)代碼以使用這些過(guò)程。 盡管我們只需為此示例編寫(xiě)最少量的代碼,但是仔細(xì)查看向?qū)鶆?chuàng)建的代碼能夠幫助我們理解與存儲(chǔ)過(guò)程接口的過(guò)程,以便除獲取數(shù)據(jù)之外還可以進(jìn)行其他操作。 對(duì)于本示例,我們將使用 Northwind 示例數(shù)據(jù)庫(kù)中的 Customers 表。 在安裝 Northwind 數(shù)據(jù)庫(kù)時(shí),其中并不包含用于更新、插入或刪除客戶(hù)的存儲(chǔ)過(guò)程,但 Visual Studio .NET 中的 DataAdapter 配置向?qū)Э珊芊奖愕鼐帉?xiě)一些存儲(chǔ)過(guò)程。 開(kāi)始一個(gè)新的 Windows Application 項(xiàng)目。 在空白的 Form1 上,放置一個(gè) DataGrid和兩個(gè)按鈕。 跟前面一樣,將 DataGrid 的 Anchor 屬性更改為定位到所有四條邊。 將按鈕命名為 btnFill 和 btnUpdate,并將其 Text 屬性分別更改為 Fill 和 Update。 轉(zhuǎn)至 Toolbox 的 Data 選項(xiàng)卡,將一個(gè) SqlDataAdapter 控件拖到窗體上然后釋放。 這樣將打開(kāi)一個(gè) DataAdapter 配置向?qū)А?單擊 Next 按鈕,開(kāi)始在向?qū)е休斎胄畔ⅰ?/p> 首先,需要選擇一個(gè)到 Northwind 數(shù)據(jù)庫(kù)的連接,或者,如果列表中沒(méi)有可用連接,則單擊 New Connection按鈕,創(chuàng)建一個(gè)連接。 然后單擊 Next 按鈕。 下一個(gè)屏幕中包含用于訪問(wèn)數(shù)據(jù)的三種可選途徑。 該屏幕如圖 2 所示。 圖 2. 為 DataAdapter 選擇數(shù)據(jù)訪問(wèn)類(lèi)型 在此處,大多數(shù)演示軟件示例會(huì)使用第一個(gè)選項(xiàng)以使用 SQL 語(yǔ)句。 然而,我們將改用第二個(gè)選項(xiàng),并讓向?qū)槲覀儎?chuàng)建一些存儲(chǔ)過(guò)程。 選擇 Create new stored procedures 選項(xiàng),然后單擊 Next 按鈕。 下一個(gè)屏幕需要一條 SQL 語(yǔ)句以指明初始時(shí)從數(shù)據(jù)庫(kù)中獲取的數(shù)據(jù)。 然而,并不會(huì)直接使用此 SQL 語(yǔ)句。 此 SQL 語(yǔ)句中的信息將用于構(gòu)建進(jìn)行實(shí)際數(shù)據(jù)訪問(wèn)的存儲(chǔ)過(guò)程。 為了使此示例保持簡(jiǎn)單明了,請(qǐng)輸入 SQL 語(yǔ)句 SELECT * FROM Customers,然后按 Next 按鈕。 在此處,向?qū)б筇峁⒁獎(jiǎng)?chuàng)建的存儲(chǔ)過(guò)程的名稱(chēng)。 有四個(gè)存儲(chǔ)過(guò)程 — 選擇、更新、插入和刪除操作。 按以下方式命名:
保持選中 Yes, create them in the database for me 選項(xiàng)。 此時(shí),向?qū)聊粦?yīng)該類(lèi)似圖 3 所示。 圖 3. 為 DataAdapter 向?qū)⒁獎(jiǎng)?chuàng)建的存儲(chǔ)過(guò)程命名 單擊 Next 按鈕。 向?qū)?chuàng)建這些存儲(chǔ)過(guò)程,并在狀態(tài)屏幕上顯示其進(jìn)度。 完成后,您可以單擊 Finish按鈕,退出向?qū)А?/p> 該向?qū)?chuàng)建了一個(gè)完全配置好的 DataAdapter,但并未創(chuàng)建 DataSet 來(lái)容納數(shù)據(jù)。 下一步,我們將進(jìn)行該操作。 從 Toolbox 的 Data 選項(xiàng)卡中,拖過(guò)一個(gè) DataSet 控件。 當(dāng)顯示其配置屏幕時(shí),選擇 Untyped dataset。 現(xiàn)在,就可利用該 DataAdapter 填充該數(shù)據(jù)集了。 在 btnFill 的 Click事件中,加入以下兩行代碼: SqlDataAdapter1.Fill(DataSet1, "Customers") DataGrid1.DataSource = DataSet1.Tables("Customers") 在 btnUpdate 的 Click 事件中,加入以下一行: SqlDataAdapter1.Update(DataSet1, "Customers") 現(xiàn)在,我們就擁有了一個(gè)可正常工作的演示軟件,它使用存儲(chǔ)過(guò)程進(jìn)行數(shù)據(jù)訪問(wèn)。 您可以運(yùn)行該程序,并單擊 Fill 按鈕以獲取網(wǎng)格中的用戶(hù)列表。 然后,您可以在網(wǎng)格中編輯用戶(hù)數(shù)據(jù),并選擇 Update 按鈕將這些更改返回到數(shù)據(jù)庫(kù)中。 注如果編輯第一列,也就是 CustomerID,將發(fā)生異常,因?yàn)槟荒茉?SQL Server 中更新一條數(shù)據(jù)庫(kù)記錄中的主鍵。 查看一下向?qū)傻拇a還是很有指導(dǎo)意義的,所有這些代碼最初都隱藏在 Windows Form Designer generated code 區(qū)域中。 單擊該區(qū)域相應(yīng)的加號(hào),展開(kāi)該代碼。 注意以下代碼,這些代碼會(huì)實(shí)例化 SQLDataAdapter及其所需的四個(gè)命令對(duì)象: Me.SqlDataAdapter1 = New System.Data.SqlClient.SqlDataAdapter() Me.SqlSelectCommand1 = New System.Data.SqlClient.SqlCommand() Me.SqlInsertCommand1 = New System.Data.SqlClient.SqlCommand() Me.SqlUpdateCommand1 = New System.Data.SqlClient.SqlCommand() Me.SqlDeleteCommand1 = New System.Data.SqlClient.SqlCommand() 再往下,您將看到用于配置每個(gè)命令對(duì)象并為其創(chuàng)建參數(shù)集合的代碼。 這段代碼類(lèi)似于前面的使用帶參數(shù)的存儲(chǔ)過(guò)程的示例中的代碼。 然而,向?qū)傻拇a使用了附加的一些參數(shù)屬性,以允許這些參數(shù)屬性與更改數(shù)據(jù)的各種存儲(chǔ)過(guò)程一起使用。 例如,用于為 SQLInsertCommand1 創(chuàng)建 CompanyName 參數(shù)的代碼: Me.SqlInsertCommand1.Parameters.Add(New _ System.Data.SqlClient.SqlParameter("@CompanyName", _ System.Data.SqlDbType.NVarChar, 40, "CompanyName")) 在前面的示例中,我們僅為參數(shù)名稱(chēng)、數(shù)據(jù)類(lèi)型和長(zhǎng)度設(shè)置了屬性。 這段代碼還將該參數(shù)的 SourceColumn 屬性設(shè)置為 CompanyName 值。 該屬性表明了該數(shù)據(jù)集的 CustomersDataTable 中與此參數(shù)對(duì)應(yīng)的字段。 這就允許在插入操作中將 DataTable 中的值自動(dòng)插入該參數(shù)的 Value 屬性中。 讓我們更詳細(xì)地討論一下這一點(diǎn)。 當(dāng) SQLDataAdapter 的 Update 方法被調(diào)用時(shí),它會(huì)更新 DataSet 中的單個(gè) DataTable。 對(duì) DataTable 逐行進(jìn)行檢查,查找需要更新、插入或刪除的行。 當(dāng)發(fā)現(xiàn)要將新的一行插入數(shù)據(jù)庫(kù)時(shí),SQLDataAdapter 會(huì)使用其 InsertCommand 屬性所設(shè)定的 Command 對(duì)象。 在這里,該 Command 對(duì)象訪問(wèn)了 MSDNInsertCustomer 存儲(chǔ)過(guò)程。 在能運(yùn)行該存儲(chǔ)過(guò)程之前,必須從正被插入的行填充每個(gè)參數(shù)的 Value 屬性。 用于配置 SQLDataAdapter1的代碼將該存儲(chǔ)過(guò)程的每個(gè)參數(shù)與 DataTable 中的相應(yīng)字段關(guān)聯(lián)起來(lái)。 這就允許將新的 DataTable 行的數(shù)據(jù)自動(dòng)傳送到該存儲(chǔ)過(guò)程的參數(shù)。 其他存儲(chǔ)過(guò)程的參數(shù)可類(lèi)似地進(jìn)行配置。 有一個(gè)不同之處值得注意。 其他存儲(chǔ)過(guò)程會(huì)傳入 DataTable中數(shù)據(jù)的初始值,這些值用于檢查數(shù)據(jù)在您不知情的情況下未發(fā)生更改。 也就是說(shuō),如果您獲取了某些數(shù)據(jù),而在您嘗試更新之前別人已經(jīng)進(jìn)行了更改,您則會(huì)得到一個(gè)并發(fā)異常。 啟動(dòng)上述程序,獲取客戶(hù)信息,然后使用某個(gè)工具(如 SQL Enterprise Manager)更改記錄中的某項(xiàng)內(nèi)容,您就可以看到這種情況發(fā)生。 如果您在示例程序中更改同一條記錄并試圖更新,則會(huì)得到一個(gè)并發(fā)異常。 從存儲(chǔ)過(guò)程返回值 以上示例在一個(gè)方面存在著不足。 Northwind Customers 表使用了字母數(shù)字主鍵,且應(yīng)用程序在插入數(shù)據(jù)時(shí)必須組成這些主鍵。 也就是說(shuō),如果您利用以上的程序插入一條新記錄,則必須自己為 CustomerID 創(chuàng)建一個(gè) 5 個(gè)字符的值。 在實(shí)際軟件中,更常見(jiàn)的做法是為新記錄自動(dòng)生成主鍵。 這種主鍵通常是一個(gè)按順序分配的長(zhǎng)整數(shù)。 有兩種基本方法可用于為新記錄設(shè)置主鍵。 應(yīng)用程序可以調(diào)用一個(gè)存儲(chǔ)過(guò)程以生成下一個(gè)可用的 ID,然后該應(yīng)用程序可以將該 ID 直接放入 DataSet 的新行中。 或者,用于插入記錄的存儲(chǔ)過(guò)程可以為該記錄派生新的 ID,然后作為一個(gè)返回值將其傳遞回該應(yīng)用程序中。 第一種方法需要一些額外的邏輯以獲取新 ID 并將其放到新記錄中的合適位置處。 使用存儲(chǔ)過(guò)程進(jìn)行插入操作與以上的示例類(lèi)似。 然而,第二種方法需要一種新的參數(shù)用于存儲(chǔ)過(guò)程。 到目前為止,我們所見(jiàn)到的所有參數(shù)都是默認(rèn)類(lèi)型,也就是一個(gè)輸入?yún)?shù)。 實(shí)際上,有四種類(lèi)型的參數(shù):
當(dāng)存儲(chǔ)過(guò)程為主鍵生成一個(gè)新值時(shí),常用的做法是利用該存儲(chǔ)過(guò)程中的一條 RETURN 語(yǔ)句返回該值,因此,用于訪問(wèn)該值的參數(shù)類(lèi)型是 ReturnValue 參數(shù)。 ReturnValue 參數(shù)與其他類(lèi)型的參數(shù)之間有一個(gè)重要的不同之處。 在通常情況下,在 ADO.NET 中為 Command對(duì)象配置各個(gè)參數(shù)的順序無(wú)關(guān)緊要。 各個(gè)參數(shù)的名稱(chēng)可用于將其與存儲(chǔ)過(guò)程中的相應(yīng)參數(shù)進(jìn)行匹配。 然而,對(duì)于 ReturnValue參數(shù)而言,它必須 是該列表中的第一個(gè)參數(shù)。 這就意味著,當(dāng)您為一個(gè) Command 對(duì)象配置 ReturnValue參數(shù)時(shí),必須在代碼中首先配置這個(gè)參數(shù),這樣它就可以獲取集合中的第一個(gè)數(shù)字索引。 如果您首先配置任何其他的參數(shù),ReturnValue參數(shù)將不會(huì)正常運(yùn)行。 為了演示帶返回值的存儲(chǔ)過(guò)程的使用,我們將執(zhí)行一個(gè)示例 — 將一條記錄插入 Northwind Products 表。 這個(gè)表被設(shè)置為利用一個(gè) Identity 列自動(dòng)生成新的產(chǎn)品 ID。 不巧的是,Northwind 示例數(shù)據(jù)庫(kù)中并不包含一個(gè)可完成我們想要的操作的存儲(chǔ)過(guò)程,因此,在進(jìn)行本示例的剩余部分之前,我們必須在數(shù)據(jù)庫(kù)中插入相應(yīng)的存儲(chǔ)過(guò)程。 轉(zhuǎn)至 Visual Studio .NET 中的 Server Explorer。 打開(kāi) SQL Server 節(jié)點(diǎn),然后打開(kāi)相應(yīng) SQL Server 實(shí)例的節(jié)點(diǎn)。 然后打開(kāi) Northwind 數(shù)據(jù)庫(kù)節(jié)點(diǎn)。 右鍵單擊 Stored Procedures 節(jié)點(diǎn),然后選擇 New Stored Procedure。 在所顯示的編輯窗口中,用以下文本替換其中所有的文本: ALTER PROCEDURE dbo.MSDNInsertProduct ( @ProductName nvarchar(40), @SupplierID int, @CategoryID int, @QuantityPerUnit nvarchar(20), @UnitPrice money, @UnitsInStock smallint, @UnitsOnOrder smallint, @ReorderLevel smallint, @Discontinued bit ) AS declare @ProductID int SET NOCOUNT OFF; INSERT INTO Products(ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued) VALUES (@ProductName, @SupplierID, @CategoryID, @QuantityPerUnit, @UnitPrice, @UnitsInStock, @UnitsOnOrder, @ReorderLevel, @Discontinued); SELECT @ProductID = @@IDENTITY RETURN @ProductID 現(xiàn)在,關(guān)閉編輯窗口,將詢(xún)問(wèn)您是否想要保存更改,單擊 Yes。 該存儲(chǔ)過(guò)程已被保存在數(shù)據(jù)庫(kù)中,且將被命名為 MSDNInsertProduct。 現(xiàn)在,我們就可以編寫(xiě)代碼來(lái)使用該存儲(chǔ)過(guò)程了。 創(chuàng)建一個(gè)新的 Windows 應(yīng)用程序,在空白 Form1 上,放置一個(gè) DataGrid,并將其定位到所有四條邊。 另外,添加名為 btnFill 和 btnInsertProduct的兩個(gè)按鈕。 將 btnFill 的 Text 屬性設(shè)置為 Fill 而將 btnInsertProduct 的 Text 屬性設(shè)置為 Insert Product。 在 btnFill 的 click 事件中,放入以下代碼: Dim sConnectionString As String = _ "server=localhost;uid=sa;pwd=;database=Northwind" Dim sSQL As String = "SELECT * FROM Products" Dim daGetProducts As New SqlDataAdapter(sSQL, sConnectionString) Dim dsProducts As New DataSet() daGetProducts.Fill(dsProducts, "Products") DataGrid1.DataSource = dsProducts 這段代碼與本文中前面所見(jiàn)的代碼幾乎完全相同,因此我們不再討論。 如有必要,不要忘記更改連接字符串,并將 SQLClient 命名空間的 Imports 語(yǔ)句放在該項(xiàng)目代碼的開(kāi)始位置處。 然后將以下代碼放到 btnInsertProduct 的 Click事件中。 Dim sConnectionString As String = _ "server=localhost;uid=sa;pwd=;database=Northwind" Dim cnNorthwind As New SqlConnection(sConnectionString) Dim cmdInsertProduct As New SqlCommand("MSDNInsertProduct", cnNorthwind) cmdInsertProduct.CommandType = CommandType.StoredProcedure ' Set up parameters for stored procedure cmdInsertProduct.Parameters.Add(New SqlParameter("@RETURN_VALUE", SqlDbType.Int, 4, "ProductID")) cmdInsertProduct.Parameters("@RETURN_VALUE").Direction = ParameterDirection.ReturnValue cmdInsertProduct.Parameters.Add(New SqlParameter("@ProductName", _ SqlDbType.NVarChar, 40, "ProductName")) cmdInsertProduct.Parameters.Add(New SqlParameter("@SupplierID", _ SqlDbType.Int, 4, "SupplierID")) cmdInsertProduct.Parameters.Add(New SqlParameter("@CategoryID", _ SqlDbType.Int, 4, "CategoryID")) cmdInsertProduct.Parameters.Add(New SqlParameter("@QuantityPerUnit", _ SqlDbType.NVarChar, 20, "QuantityPerUnit")) cmdInsertProduct.Parameters.Add(New SqlParameter("@UnitPrice", _ SqlDbType.Money, 8, "UnitPrice")) cmdInsertProduct.Parameters.Add(New SqlParameter("@UnitsInStock", _ SqlDbType.SmallInt, 2, "UnitsInStock")) cmdInsertProduct.Parameters.Add(New SqlParameter("@UnitsOnOrder", _ SqlDbType.SmallInt, 2, "UnitsOnOrder")) cmdInsertProduct.Parameters.Add(New SqlParameter("@ReorderLevel", _ SqlDbType.SmallInt, 2, "ReorderLevel")) cmdInsertProduct.Parameters.Add(New SqlParameter("@Discontinued", _ SqlDbType.Bit, 1, "Discontinued")) Dim daInsertProduct As New SqlDataAdapter() daInsertProduct.InsertCommand = cmdInsertProduct Dim dsProducts As DataSet = CType(DataGrid1.DataSource, DataSet) Dim drNewProduct As DataRow drNewProduct = dsProducts.Tables("Products").NewRow drNewProduct.Item("ProductName") = "Billy's Sesame Oil" drNewProduct.Item("SupplierID") = 4 drNewProduct.Item("CategoryID") = 7 drNewProduct.Item("QuantityPerUnit") = "6 10oz bottles" drNewProduct.Item("UnitPrice") = 69 drNewProduct.Item("UnitsInStock") = 12 drNewProduct.Item("UnitsOnOrder") = 0 drNewProduct.Item("ReorderLevel") = 6 drNewProduct.Item("Discontinued") = False dsProducts.Tables("Products").Rows.Add(drNewProduct) daInsertProduct.Update(dsProducts.Tables("Products")) MsgBox(drNewProduct.Item("ProductID")) 除了用于為返回值配置參數(shù)的各行外,這段代碼與我們前面所見(jiàn)的代碼很相似。 請(qǐng)注意,這是第一個(gè)參數(shù),設(shè)置此參數(shù)是為了將返回值放回到 ProductID 字段。 用于將新行插入數(shù)據(jù)集的代碼是標(biāo)準(zhǔn)的 ADO.NET 代碼,因此我們將不再詳述。 它為產(chǎn)品記錄創(chuàng)建一個(gè)具有合適結(jié)構(gòu)的新行(利用 products DataTable 的 NewRow 方法),將數(shù)據(jù)放入行中,最后將該行加入 products DataTable 的 Rows 集合中。 現(xiàn)在,運(yùn)行該程序以進(jìn)行測(cè)試。 單擊 Fill 按鈕,但是不對(duì)網(wǎng)格中的任何數(shù)據(jù)進(jìn)行更改。 然后按 Insert Product按鈕。 將插入一條對(duì)應(yīng)于 Billy's Sesame Oil 的新產(chǎn)品記錄,且一個(gè)消息框?qū)@示針對(duì)該記錄返回的 ProductID。 您也可以在網(wǎng)格中打開(kāi) Products 表,滾動(dòng)瀏覽到底部,即可注意到已添加的新記錄。 使用 Server Explorer 編寫(xiě)參數(shù)代碼 以上的代碼編寫(xiě)起來(lái)乏味而重復(fù)。 然而,DataAdapter 配置向?qū)崾緦?shí)際上 Visual Studio 可以替我們編寫(xiě)這些代碼。 DataAdapter 配置向?qū)橐粋€(gè)完整配置中的所有四個(gè)存儲(chǔ)過(guò)程(Select、Update、Insert 和 Delete 各一個(gè))生成代碼。 假設(shè)您正需要如上例所示的某一個(gè)存儲(chǔ)過(guò)程的代碼,那該如何呢? 您仍然可以走一條捷徑。 要獲得只是與一個(gè)存儲(chǔ)過(guò)程接口的預(yù)編寫(xiě)代碼,只需展開(kāi) Server Explorer 以顯示您想要使用的存儲(chǔ)過(guò)程,然后將該存儲(chǔ)過(guò)程拖到設(shè)計(jì)界面上即可。 您將看到只是為該存儲(chǔ)過(guò)程創(chuàng)建的 DataAdapter 和 Command對(duì)象,且該代碼的設(shè)計(jì)器部分將包含為該存儲(chǔ)過(guò)程配置各個(gè)參數(shù)所需的全部代碼。 您可原封不動(dòng)地使用該代碼,也可根據(jù)自己的需要而復(fù)制并改寫(xiě)該代碼。 小結(jié) 本文中的各個(gè)示例仍然是演示軟件,但是至少這些示例向您展示的使用存儲(chǔ)過(guò)程的相關(guān)信息足以使您開(kāi)始編寫(xiě)自己的軟件。 當(dāng)然了,您需要理解您將要使用的存儲(chǔ)過(guò)程,并且可能需要詢(xún)問(wèn)數(shù)據(jù)庫(kù)管理員 (DBA) 或其他小組成員以獲得這些信息。 對(duì)于復(fù)雜系統(tǒng)而言,存儲(chǔ)過(guò)程具有很多優(yōu)點(diǎn)。 我希望您已從本文中學(xué)習(xí)到足夠的知識(shí),而不會(huì)對(duì)開(kāi)始使用存儲(chǔ)過(guò)程而心存畏懼。 在最初的一些嘗試中,您可能想要使用 DataAdapter 向?qū)Щ蛘?Server Explorer 為您編寫(xiě)大多數(shù)代碼,但如果您能夠在需要的時(shí)候編寫(xiě)自己的訪問(wèn)代碼,那么您工作起來(lái)將事半功倍。 |
|
來(lái)自: ycgclf > 《我的圖書(shū)館》