導(dǎo)言:
本教程的Data Access Layer (DAL)使用的是類型化的數(shù)據(jù)集(Typed DataSets).就像我們?cè)诘谝徽隆秳?chuàng)建一個(gè)數(shù)據(jù)訪問(wèn)層》里探討的一樣,該類型化的數(shù)據(jù)集由強(qiáng)類型的DataTable和TableAdapter構(gòu)成。DataTable描繪的是系統(tǒng)里的邏輯實(shí)體而TableAdapter引用相關(guān)數(shù)據(jù)庫(kù)執(zhí)行數(shù)據(jù)訪問(wèn),包括對(duì)DataTable填充數(shù)據(jù)、執(zhí)行返回標(biāo)量數(shù)據(jù)(scalar data)的請(qǐng)求、添加,更新,刪除數(shù)據(jù)庫(kù)里的記錄等.
TableAdapter執(zhí)行的SQL命令要么是某個(gè)特定的SQL statements,比如SELECT columnList FROM TableName;要么是存儲(chǔ)過(guò)程.本教程前面部分的TableAdapter使用的是SQL statements.不過(guò)很多開發(fā)者和數(shù)據(jù)庫(kù)管理員基于安全、便于維護(hù)等方面的考慮,偏愛使用存儲(chǔ)過(guò)程;不過(guò)也有的人出于靈活性的考慮偏愛使用SQL statement.就我自己而言,我也偏向于存儲(chǔ)過(guò)程.在前面的文章,出于簡(jiǎn)化的目的我選用的是SQL statements.
當(dāng)定義一個(gè)新TableAdapter或添加新方法時(shí),使用TableAdapter的設(shè)置向?qū)?,我們可以很容易的?chuàng)建新的或使用現(xiàn)有的存儲(chǔ)過(guò)程.在本文,我們將考察如何使用設(shè)置向?qū)ё詣?dòng)的生產(chǎn)存儲(chǔ)過(guò)程。在下一章我們考察如何設(shè)置TableAdapter的方法使用現(xiàn)有的或手動(dòng)創(chuàng)建存儲(chǔ)過(guò)程.
注意:關(guān)于討論到底使用存儲(chǔ)過(guò)程還是使用SQL statements的問(wèn)題,可參考Rob Howard的博客文章《Don't Use Stored Procedures Yet?》(http://weblogs.asp.net/rhoward/archive/2003/11/17/38095.aspx)和Frans Bouma的博客文章《Stored Procedures are Bad, M'Kay?》(http://weblogs.asp.net/fboue/2003/11/18/38178.aspx)
存儲(chǔ)過(guò)程基礎(chǔ)
一個(gè)存儲(chǔ)過(guò)程由一系列的T-SQL statement組成,當(dāng)調(diào)用該存儲(chǔ)過(guò)程時(shí)就執(zhí)行這些T-SQL statement.存儲(chǔ)過(guò)程可以接受0到多個(gè)輸入?yún)?shù),返回標(biāo)量值、輸出參數(shù),或最常見的返回SELECT查詢值.
注意:存儲(chǔ)過(guò)程Stored procedures也經(jīng)常引用為“sprocs” or “SPs”.
可以使用T-SQL statement語(yǔ)句CREATE PROCEDURE來(lái)創(chuàng)建存儲(chǔ)過(guò)程.比如下面的T-SQL腳本創(chuàng)建了一個(gè)名為GetProductsByCategoryID的存儲(chǔ)過(guò)程,它有一個(gè)名為 @CategoryID的參數(shù),并且將表Products里與CategoryID值相吻合的那條記錄的ProductID, ProductName, UnitPrice,以及Discontinued值返回.
CREATE PROCEDURE GetProductsByCategoryID
(
@CategoryID int
)
AS
SELECT ProductID, ProductName, UnitPrice, Discontinued
FROM Products
WHERE CategoryID = @CategoryID
創(chuàng)建后,我們可以用下面的代碼調(diào)用它:
EXEC GetProductsByCategory categoryID
注意:在下篇文章我們將在Visual Studio IDE集成環(huán)境里創(chuàng)建存儲(chǔ)過(guò)程.不過(guò)在本文,我們將用TableAdapter向?qū)?lái)自動(dòng)創(chuàng)建存儲(chǔ)過(guò)程.
除了返回?cái)?shù)據(jù)外,我們還可以在一個(gè)事務(wù)里用存儲(chǔ)過(guò)程執(zhí)行多條數(shù)據(jù)庫(kù)命令.比如,假如有一個(gè)名為DeleteCategory的存儲(chǔ)過(guò)程,其包含一個(gè)輸入?yún)?shù)@CategoryID,并執(zhí)行2個(gè)DELETE statemets,第一個(gè)是刪除相關(guān)的products,第二個(gè)是刪除category。存儲(chǔ)過(guò)程里面的多個(gè)statements并不是自動(dòng)的封裝在一個(gè)事務(wù)里的.我們應(yīng)添加額外的T-SQL commands以確保存儲(chǔ)過(guò)程里的多條數(shù)據(jù)庫(kù)命令當(dāng)成原子操作處理.我們將在后面的內(nèi)容考察如何用事務(wù)來(lái)封裝存儲(chǔ)過(guò)程的命令.
當(dāng)在體系的某個(gè)層使用存儲(chǔ)過(guò)程時(shí),Data Access Layer的方法將調(diào)用某個(gè)具體的存儲(chǔ)過(guò)程而不是發(fā)出一個(gè)SQL statement命令.這樣一來(lái)我們可以發(fā)現(xiàn)、分析發(fā)出的查詢命令.并可以更清楚的看到數(shù)據(jù)庫(kù)是如何使用的.有關(guān)存儲(chǔ)過(guò)程基本原理的更多信息,可參考本文結(jié)束部分的延伸閱讀.
第一步:創(chuàng)建數(shù)據(jù)訪問(wèn)層高級(jí)場(chǎng)景的Web頁(yè)面
在開始之前,讓我們花點(diǎn)時(shí)間創(chuàng)建本文及后面幾篇文章要用到的頁(yè)面。新建一個(gè)名為AdvancedDAL的文件夾,然后添加如下的ASP.NET頁(yè)面,記得使用母版頁(yè)Site.master:
Default.aspx
NewSprocs.aspx
ExistingSprocs.aspx
JOINs.aspx
AddingColumns.aspx
ComputedColumns.aspx
EncryptingConfigSections.aspx
ManagedFunctionsAndSprocs.aspx
圖1:添加相關(guān)的頁(yè)面
像其它文件夾一樣,Default.aspx頁(yè)面將列出本部分的內(nèi)容,記得SectionLevelTutorialListing.ascx用戶控件提供了該功能。因此,將其從解決資源管理器里拖放到Default.aspx頁(yè)面.
圖2:將SectionLevelTutorialListing.ascx用戶控件拖到Default.aspx頁(yè)面
最后,將這些頁(yè)面添加到Web.sitemap文件里。特別的,把下面的代碼放在“Working with Batched Data”
siteMapNode>標(biāo)簽后面:
siteMapNode url="~/AdvancedDAL/Default.aspx"
title="Advanced DAL Scenarios"
description="Explore a number of advanced Data Access Layer scenarios.">
siteMapNode url="~/AdvancedDAL/NewSprocs.aspx"
title="Creating New Stored Procedures for TableAdapters"
description="Learn how to have the TableAdapter wizard automatically
create and use stored procedures." />
siteMapNode url="~/AdvancedDAL/ExistingSprocs.aspx"
title="Using Existing Stored Procedures for TableAdapters"
description="See how to plug existing stored procedures into a
TableAdapter." />
siteMapNode url="~/AdvancedDAL/JOINs.aspx"
title="Returning Data Using JOINs"
description="Learn how to augment your DataTables to work with data
returned from multiple tables via a JOIN query." />
siteMapNode url="~/AdvancedDAL/AddingColumns.aspx"
title="Adding DataColumns to a DataTable"
description="Master adding new columns to an existing DataTable." />
siteMapNode url="~/AdvancedDAL/ComputedColumns.aspx"
title="Working with Computed Columns"
description="Explore how to work with computed columns when using
Typed DataSets." />
siteMapNode url="~/AdvancedDAL/EncryptingConfigSections.aspx"
title="Protected Connection Strings in Web.config"
description="Protect your connection string information in
Web.config using encryption." />
siteMapNode url="~/AdvancedDAL/ManagedFunctionsAndSprocs.aspx"
title="Creating Managed SQL Functions and Stored Procedures"
description="See how to create SQL functions and stored procedures
using managed code." />
/siteMapNode>
更新Web.sitemap文件后,花點(diǎn)時(shí)間在瀏覽器里查看,左邊的菜單將包括本部分的內(nèi)容.
圖3:網(wǎng)站地圖現(xiàn)在包含了不部分的頁(yè)面
第二步:設(shè)置TableAdapter創(chuàng)建新的存儲(chǔ)過(guò)程
我們?cè)趡/App_Code/DAL文件夾里創(chuàng)建一個(gè)類型化的DataSet,名稱為NorthwindWithSprocs.xsd.由于我們?cè)谝郧暗慕坛汤镆呀?jīng)詳細(xì)探討了創(chuàng)建細(xì)節(jié),因此我們這里一筆帶過(guò),如果你想知道詳細(xì)的創(chuàng)建過(guò)程請(qǐng)參閱前面的第1章《創(chuàng)建一個(gè)數(shù)據(jù)訪問(wèn)層》在DAL文件夾上右擊鼠標(biāo)選“添加新項(xiàng)”,選DataSet模板,如圖4所示.
圖4:新建一個(gè)名為NorthwindWithSprocs.xsd的數(shù)據(jù)集
這樣將會(huì)創(chuàng)建一個(gè)新的類型化的DataSet,打開設(shè)計(jì)器,創(chuàng)建一個(gè)新的TableAdapter,展開TableAdapter設(shè)置向?qū)?向?qū)У牡谝徊绞亲屛覀冞x擇要連接的數(shù)據(jù)庫(kù).在下拉列表里有一個(gè)連接到Northwind數(shù)據(jù)庫(kù)的連接字符串,選中它,再點(diǎn)下一步。接下來(lái)的界面讓我們選擇TableAdapter以哪種方式訪問(wèn)數(shù)據(jù)庫(kù).在以前的教程里我們選擇的是“Use SQL statements”,不過(guò)在本文我們選第二項(xiàng):“Create new stored procedures”,點(diǎn)下一步.
圖5:設(shè)置TableAdpater創(chuàng)建新的存儲(chǔ)過(guò)程
接下來(lái),我們要指定主查詢(main query).我們將創(chuàng)建一個(gè)存儲(chǔ)過(guò)程來(lái)包含SELECT查詢.
使用下面的SELECT查詢:
SELECT ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
ReorderLevel, Discontinued
FROM Products
圖6:鍵入SELECT查詢
注意:在名為Northwind的數(shù)據(jù)集里的ProductsTableAdapter的主查詢與上面本文定義的主查詢有所不同。那個(gè)主查詢還返回了每個(gè)產(chǎn)品的category名稱和company名稱.不過(guò)在后面的文章我們將對(duì)本文的TableAdapter添加這些相關(guān)的代碼.再點(diǎn)“Advanced Options”按鈕.我們可以指定是否讓向?qū)門ableAdapter自動(dòng)生成insert, update和delete statements;是否使用開發(fā)式并發(fā)操作(optimistic concurrency);是否完成inserts 和 update操作后刷新數(shù)據(jù)表.在默認(rèn)情況下,自動(dòng)選中“Generate Insert, Update and Delete statements”選項(xiàng)。另外,本文不用選擇“Use optimistic concurrency”項(xiàng).當(dāng)選擇自動(dòng)創(chuàng)建存儲(chǔ)過(guò)程時(shí),“Refresh the data table”項(xiàng)將被忽略掉.不管是否選中該項(xiàng),最終的insert 和update存儲(chǔ)過(guò)程都會(huì)檢索剛添加或剛更新(just-inserted or just-updated record)的記錄,我們將在第三步看到.
圖7:選中“Generate Insert, Update and Delete statements”項(xiàng)
注意:當(dāng)選中“Use optimistic concurrency”項(xiàng)的時(shí)候,向?qū)?huì)在WHERE語(yǔ)句里添加額外的條件,當(dāng)其它列的值發(fā)生改動(dòng)的話,將阻止數(shù)據(jù)更新.關(guān)于使用TableAdapter內(nèi)置的optimistic concurrency功能請(qǐng)參閱第21章《實(shí)現(xiàn)開放式并發(fā)》輸入SELECT主查詢并選取“Generate Insert, Update and Delete statements”項(xiàng)后,點(diǎn)下一步,接下來(lái)的界面,如圖8所示,讓我們?yōu)閟electing, inserting, updating, 和deleting數(shù)據(jù)的存儲(chǔ)過(guò)程命名.將這些存儲(chǔ)過(guò)程的名字改為Products_Select, Products_Insert, Products_Update, 和Products_Delete.
圖8:為存儲(chǔ)過(guò)程重命名
向?qū)?chuàng)建了4個(gè)存儲(chǔ)過(guò)程,點(diǎn)“Preview SQL Script”按鈕,你可以在Preview SQL Script 對(duì)話框里將腳本保存在一個(gè)文件里或復(fù)制到剪貼板.
圖9:預(yù)覽生成的存儲(chǔ)過(guò)程
對(duì)存儲(chǔ)過(guò)程重命名后,點(diǎn)下一步,對(duì)TableAdapter相應(yīng)的方法命名.就像使用SQL statements一樣,我們可以創(chuàng)建方法來(lái)填充一個(gè)現(xiàn)有的DataTable或返回一個(gè)新的DataTable;我們也一個(gè)指定TableAdapter是否采用DB-Direct模式來(lái)插入、更新、刪除記錄.全選這3項(xiàng),只不過(guò)將Return a DataTable方法重命名為GetProducts,如圖10所示:
圖10:將方法重命名為Fill 和GetProducts
點(diǎn)Next總覽向?qū)?zhí)行的步驟.點(diǎn)Finish按鈕完成設(shè)置.一旦向?qū)ЫY(jié)束后,將返回DataSet設(shè)計(jì)器,它此時(shí)將包括ProductsDataTable.
圖11:DataSet設(shè)計(jì)器將顯示剛剛添加的ProductsDataTable
第三步:考察剛剛創(chuàng)建的存儲(chǔ)過(guò)程
我們?cè)诘诙嚼镉孟驅(qū)?chuàng)建了選擇、插入、更新、刪除數(shù)據(jù)的存儲(chǔ)過(guò)程.這些存儲(chǔ)過(guò)程可以通過(guò)Visual Studio查看或修改.打開服務(wù)器資源管理器,點(diǎn)到數(shù)據(jù)庫(kù)的存儲(chǔ)過(guò)程文件夾。如圖12所示,Northwind數(shù)據(jù)庫(kù)包含了4個(gè)新的存儲(chǔ)過(guò)程,Products_Delete, Products_Insert, Products_Select, and Products_Update.
圖12:可以在Stored Procedures文件夾里找到我們創(chuàng)建的4個(gè)存儲(chǔ)過(guò)程
注意:如果你看不到服務(wù)器資源管理器,點(diǎn)“View”菜單,選Server Explorer項(xiàng).如果你無(wú)法找到新創(chuàng)建的存儲(chǔ)過(guò)程,右擊Stored Procedures文件夾,選“刷新”.
要查看或修改某個(gè)存儲(chǔ)過(guò)程,在服務(wù)器資源管理器里雙擊其名字或右擊該存儲(chǔ)過(guò)程,選”打開“。如13顯示的是打開Products_Delete存儲(chǔ)過(guò)程的畫面.
圖13:可以在Visual Studio里打開并修改存儲(chǔ)過(guò)程
Products_Delete和Products_Select存儲(chǔ)過(guò)程的內(nèi)容很好理解。比如下面的代碼構(gòu)成了Products_Insert存儲(chǔ)過(guò)程.
ALTER PROCEDURE dbo.Products_Insert
(
@ProductName nvarchar(40),
@SupplierID int,
@CategoryID int,
@QuantityPerUnit nvarchar(20),
@UnitPrice money,
@UnitsInStock smallint,
@UnitsOnOrder smallint,
@ReorderLevel smallint,
@Discontinued bit
)
AS
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, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice,
UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued
FROM Products
WHERE (ProductID = SCOPE_IDENTITY())
在TableAdapter向?qū)Ю锒x的SELECT查詢返回Products表里的列,這些列又作為存儲(chǔ)過(guò)程的輸入?yún)?shù)并運(yùn)用到INSERT statement中.緊接著的是一個(gè)SELECT查詢,返回Products表里最新添加的記錄的各列的值(包括ProductID)。當(dāng)使用Batch Update模式添加一個(gè)新記錄時(shí),刷新功能是很有用的。因?yàn)樗鼘⒆钚绿砑拥腜roductRow instances實(shí)例的ProductID屬性賦值為數(shù)據(jù)庫(kù)指派的自增值.
下面的代碼說(shuō)明了該功能.代碼創(chuàng)建了基于NorthwindWithSprocs數(shù)據(jù)集的ProductsTableAdapter以及ProductsDataTable。要向數(shù)據(jù)庫(kù)添加一個(gè)新的產(chǎn)品,我們要?jiǎng)?chuàng)建一個(gè)ProductsRow instance實(shí)例,對(duì)其賦值,并調(diào)用TableAdapter的Update方法,再傳遞給ProductsDataTable.在內(nèi)部,TableAdapter的Update方法遍歷傳遞給DataTable的所有ProductsRow instance實(shí)例(在本例,只有一個(gè)。因?yàn)槲覀冎惶砑恿艘粋€(gè)產(chǎn)品),并執(zhí)行相應(yīng)的insert, update, 或delete命令。此時(shí),執(zhí)行Products_Insert存儲(chǔ)過(guò)程,其向Products表添加一條新記錄,并返回該記錄的詳細(xì)信息,然后更新ProductsRow instance實(shí)例的ProductID值。Update方法完成后,我們就可以通過(guò)ProductsRow的ProductID屬性訪問(wèn)新添加記錄的ProductID值了.
// Create the ProductsTableAdapter and ProductsDataTable
NorthwindWithSprocsTableAdapters.ProductsTableAdapter productsAPI =
new NorthwindWithSprocsTableAdapters.ProductsTableAdapter();
NorthwindWithSprocs.ProductsDataTable products =
new NorthwindWithSprocs.ProductsDataTable();
// Create a new ProductsRow instance and set its properties
NorthwindWithSprocs.ProductsRow product = products.NewProductsRow();
product.ProductName = "New Product";
product.CategoryID = 1; // Beverages
product.Discontinued = false;
// Add the ProductsRow instance to the DataTable
products.AddProductsRow(product);
// Update the DataTable using the Batch Update pattern
productsAPI.Update(products);
// At this point, we can determine the value of the newly-added record's ProductID
int newlyAddedProductIDValue = product.ProductID;
類似的,Products_Update存儲(chǔ)過(guò)程的UPDATE statement后面也包含一個(gè)SELECT statement,如下:
ALTER PROCEDURE dbo.Products_Update
(
@ProductName nvarchar(40),
@SupplierID int,
@CategoryID int,
@QuantityPerUnit nvarchar(20),
@UnitPrice money,
@UnitsInStock smallint,
@UnitsOnOrder smallint,
@ReorderLevel smallint,
@Discontinued bit,
@Original_ProductID int,
@ProductID int
)
AS
SET NOCOUNT OFF;
UPDATE [Products]
SET [ProductName] = @ProductName, [SupplierID] = @SupplierID,
[CategoryID] = @CategoryID, [QuantityPerUnit] = @QuantityPerUnit,
[UnitPrice] = @UnitPrice, [UnitsInStock] = @UnitsInStock,
[UnitsOnOrder] = @UnitsOnOrder, [ReorderLevel] = @ReorderLevel,
[Discontinued] = @Discontinued
WHERE (([ProductID] = @Original_ProductID));
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued
FROM Products
WHERE (ProductID = @ProductID)
我們注意到該存儲(chǔ)過(guò)程有2個(gè)關(guān)于ProductID的參數(shù),即@Original_ProductID 和@ProductID,這樣以來(lái)我們就可以對(duì)主鍵值進(jìn)行改動(dòng)了.舉個(gè)例子:有一個(gè)employee(雇員)數(shù)據(jù)庫(kù),每條employee記錄都用雇員的社保號(hào)碼作為其主鍵值.要想更改某條記錄的社保號(hào)碼,必須提供新的號(hào)碼以及原始號(hào)碼.不過(guò)對(duì)Products表來(lái)說(shuō)用不著,因?yàn)榱蠵roductID是一個(gè)唯一標(biāo)識(shí)列(IDENTITY column),不應(yīng)對(duì)其更改.實(shí)際上,Products_Update存儲(chǔ)過(guò)程里的UPDATE statement并沒(méi)有包含ProductID列,因此,如果在UPDATE statement的WHERE字句里使用@Original_ProductID的話,顯得多此一舉,而應(yīng)該使用@ProductID參數(shù).當(dāng)更新某個(gè)存儲(chǔ)過(guò)程的參數(shù)時(shí),TableAdapter里所有那些調(diào)用該存儲(chǔ)過(guò)程方法都應(yīng)該進(jìn)行更新.
第四步:修改存儲(chǔ)過(guò)程的參數(shù)并更新TableAdapter
由于@Original_ProductID參數(shù)是多余的,讓我們將其從Products_Update存儲(chǔ)過(guò)程里完全清除.打開Products_Update存儲(chǔ)過(guò)程,刪除@Original_ProductID參數(shù),在UPDATE statement的WHERE字句里將@Original_ProductID改為@ProductID. 完成上述修改后,該存儲(chǔ)過(guò)程里的T-SQL看起來(lái)應(yīng)該和下面的差不多:
ALTER PROCEDURE dbo.Products_Update
(
@ProductName nvarchar(40),
@SupplierID int,
@CategoryID int,
@QuantityPerUnit nvarchar(20),
@UnitPrice money,
@UnitsInStock smallint,
@UnitsOnOrder smallint,
@ReorderLevel smallint,
@Discontinued bit,
@ProductID int
)
AS
SET NOCOUNT OFF;
UPDATE [Products] SET [ProductName] = @ProductName, [SupplierID] = @SupplierID,
[CategoryID] = @CategoryID, [QuantityPerUnit] = @QuantityPerUnit,
[UnitPrice] = @UnitPrice, [UnitsInStock] = @UnitsInStock,
[UnitsOnOrder] = @UnitsOnOrder, [ReorderLevel] = @ReorderLevel,
[Discontinued] = @Discontinued
WHERE (([ProductID] = @ProductID));
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued
FROM Products
WHERE (ProductID = @ProductID)
按Ctrl+S或點(diǎn)工具欄里的“保存”圖標(biāo),保存更改.此時(shí),Products_Update存儲(chǔ)過(guò)程不會(huì)執(zhí)行@Original_ProductID參數(shù),但TableAdapter仍然會(huì)傳遞該參數(shù).要想查看TableAdapter傳遞給Products_Update存儲(chǔ)過(guò)程的參數(shù),你可以在設(shè)計(jì)器里選中TableAdapter,轉(zhuǎn)到屬性窗口,點(diǎn)更新命令的參數(shù)集(UpdateCommand'sParameters collection)里的橢圓型區(qū)域,這樣將轉(zhuǎn)到Parameters Collection Editor對(duì)話框,如圖14所示:
圖14:對(duì)話框里列出了傳遞給Products_Update存儲(chǔ)過(guò)程的參數(shù)
要?jiǎng)h除參數(shù),只需選中它,再點(diǎn)Remove按鈕.
要刷新參數(shù)的話,你也可以在設(shè)計(jì)器里選中TableAdapter,點(diǎn)右鍵選“設(shè)置”,這將會(huì)開啟TableAdapter設(shè)置向?qū)?,它列出了用于select, insert, updat和delete的存儲(chǔ)過(guò)程,并列出了這些存儲(chǔ)過(guò)程的輸入?yún)?shù).如果你在Update下拉列表里選Products_Update的話,你可以看到該存儲(chǔ)過(guò)程包含的輸入?yún)?shù)里已經(jīng)沒(méi)有包含@Original_ProductID了(見圖15),點(diǎn)Finish將對(duì)TableAdapter使用的參數(shù)集自動(dòng)更新.
圖15:你可以通過(guò)使用TableAdapter的設(shè)置向?qū)?lái)刷新參數(shù)集
第五步:添加額外的TableAdapter方法
我們?cè)诘诙秸f(shuō)過(guò),當(dāng)創(chuàng)建一個(gè)新的TableAdapter時(shí),很容易自動(dòng)地生成相應(yīng)的存儲(chǔ)過(guò)程,同樣我們也可以向TableAdapter添加額外的方法.作為演示,讓我們向ProductsTableAdapter添加一個(gè)方法GetProductByProductID(productID),該方法將一個(gè)ProductID作為輸入?yún)?shù),并返回該產(chǎn)品的詳細(xì)信息.在ProductsTableAdapter上點(diǎn)擊右鍵,選擇“添加查詢”.
圖16:向TableAdapter添加新查詢
這將開啟TableAdapter查詢?cè)O(shè)置向?qū)?。首先,向?qū)⒃儐?wèn)以何種方式訪問(wèn)數(shù)據(jù)庫(kù),我們將創(chuàng)建一個(gè)新的存儲(chǔ)過(guò)程,因此選“Create a new stored procedure”,再點(diǎn)Next.
圖17:選中“Create a new stored procedure”項(xiàng)
接下來(lái),向?qū)г儐?wèn)我們執(zhí)行哪種查詢,是返回一系列行?一個(gè)標(biāo)量值?又或者執(zhí)行UPDATE, INSERT,或 DELETE statement.由于GetProductByProductID(productID)方法將返回一行,我們選擇“SELECT which returns row”項(xiàng),再點(diǎn)Next.
圖18:選擇“SELECT which returns row” 項(xiàng)
接下來(lái)的界面將展示TableAdapter的主查詢,其僅僅列出了存儲(chǔ)過(guò)程的名字(也就是dbo.Products_Select).將其刪除,替換為如下的SELECT statement,它返回某個(gè)具體產(chǎn)品的所有列.
SELECT ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
ReorderLevel, Discontinued
FROM Products
WHERE ProductID = @ProductID
圖19:將存儲(chǔ)過(guò)程的名字替換為一個(gè)SELECT查詢.
接下來(lái)要對(duì)創(chuàng)建的存儲(chǔ)過(guò)程命名,輸入Products_SelectByProductID,點(diǎn)Next.
圖20:將新存儲(chǔ)過(guò)程命名為Products_SelectByProductID
最后一步將要我們對(duì)自動(dòng)生成的名字重新命名,并指定是否使用Fill a DataTable模式、是否使用Return a DataTable模式,抑或這2種模式都采用.就本文而言,都選中這2項(xiàng)并將方法重命名為FillByProductID 和 GetProductByProductID.點(diǎn)Next,再點(diǎn)Finish完成設(shè)置向?qū)?
圖21:將TableAdapter的方法重命名為FillByProductID 和 GetProductByProductID
完成向?qū)Ш螅琓ableAdapter將包含一個(gè)新的可用方法——GetProductByProductID(productID),當(dāng)調(diào)用該方法時(shí),將執(zhí)行我們剛剛創(chuàng)建的Products_SelectByProductID存儲(chǔ)過(guò)程.花點(diǎn)時(shí)間在服務(wù)器資源管理器里查看該存儲(chǔ)過(guò)程,點(diǎn)Stored Procedures文件夾,并打開Products_SelectByProductID(如果你沒(méi)看到它,在Stored Procedures文件夾上右擊鼠標(biāo),選“刷新”).
請(qǐng)注意,SelectByProductID存儲(chǔ)過(guò)程將@ProductID作為輸入?yún)?shù),并執(zhí)行我們?cè)谙驅(qū)Ю镙斎氲腟ELECT Statement,如下:
ALTER PROCEDURE dbo.Products_SelectByProductID
(
@ProductID int
)
AS
SET NOCOUNT ON;
SELECT ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
ReorderLevel, Discontinued
FROM Products
WHERE ProductID = @ProductID
第六步:創(chuàng)建一個(gè)業(yè)務(wù)邏輯層類
在我們打算從表現(xiàn)層訪問(wèn)產(chǎn)品前,我們首先需要為新添加的數(shù)據(jù)集創(chuàng)建一個(gè)BLL class,在~/App_Code/BLL文件夾里創(chuàng)建一個(gè)ProductsBLLWithSprocs.cs文件,如下:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindWithSprocsTableAdapters;
[System.ComponentModel.DataObject]
public class ProductsBLLWithSprocs
{
private ProductsTableAdapter _productsAdapter = null;
protected ProductsTableAdapter Adapter
{
get
{
if (_productsAdapter == null)
_productsAdapter = new ProductsTableAdapter();
return _productsAdapter;
}
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Select, true)]
public NorthwindWithSprocs.ProductsDataTable GetProducts()
{
return Adapter.GetProducts();
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Select, false)]
public NorthwindWithSprocs.ProductsDataTable GetProductByProductID(int productID)
{
return Adapter.GetProductByProductID(productID);
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Insert, true)]
public bool AddProduct
(string productName, int? supplierID, int? categoryID,
string quantityPerUnit, decimal? unitPrice, short? unitsInStock,
short? unitsOnOrder, short? reorderLevel, bool discontinued)
{
// Create a new ProductRow instance
NorthwindWithSprocs.ProductsDataTable products =
new NorthwindWithSprocs.ProductsDataTable();
NorthwindWithSprocs.ProductsRow product = products.NewProductsRow();
product.ProductName = productName;
if (supplierID == null)
product.SetSupplierIDNull();
else
product.SupplierID = supplierID.Value;
if (categoryID == null)
product.SetCategoryIDNull();
else
product.CategoryID = categoryID.Value;
if (quantityPerUnit == null)
product.SetQuantityPerUnitNull();
else
product.QuantityPerUnit = quantityPerUnit;
if (unitPrice == null)
product.SetUnitPriceNull();
else
product.UnitPrice = unitPrice.Value;
if (unitsInStock == null)
product.SetUnitsInStockNull();
else
product.UnitsInStock = unitsInStock.Value;
if (unitsOnOrder == null)
product.SetUnitsOnOrderNull();
else
product.UnitsOnOrder = unitsOnOrder.Value;
if (reorderLevel == null)
product.SetReorderLevelNull();
else
product.ReorderLevel = reorderLevel.Value;
product.Discontinued = discontinued;
// Add the new product
products.AddProductsRow(product);
int rowsAffected = Adapter.Update(products);
// Return true if precisely one row was inserted, otherwise false
return rowsAffected == 1;
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateProduct
(string productName, int? supplierID, int? categoryID, string quantityPerUnit,
decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,
short? reorderLevel, bool discontinued, int productID)
{
NorthwindWithSprocs.ProductsDataTable products =
Adapter.GetProductByProductID(productID);
if (products.Count == 0)
// no matching record found, return false
return false;
NorthwindWithSprocs.ProductsRow product = products[0];
product.ProductName = productName;
if (supplierID == null)
product.SetSupplierIDNull();
else
product.SupplierID = supplierID.Value;
if (categoryID == null)
product.SetCategoryIDNull();
else
product.CategoryID = categoryID.Value;
if (quantityPerUnit == null)
product.SetQuantityPerUnitNull();
else
product.QuantityPerUnit = quantityPerUnit;
if (unitPrice == null)
product.SetUnitPriceNull();
else
product.UnitPrice = unitPrice.Value;
if (unitsInStock == null)
product.SetUnitsInStockNull();
else
product.UnitsInStock = unitsInStock.Value;
if (unitsOnOrder == null)
product.SetUnitsOnOrderNull();
else
product.UnitsOnOrder = unitsOnOrder.Value;
if (reorderLevel == null)
product.SetReorderLevelNull();
else
product.ReorderLevel = reorderLevel.Value;
product.Discontinued = discontinued;
// Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Delete, true)]
public bool DeleteProduct(int productID)
{
int rowsAffected = Adapter.Delete(productID);
// Return true if precisely one row was deleted, otherwise false
return rowsAffected == 1;
}
}
該類和以前章節(jié)所創(chuàng)建的ProductsBLL class類差不多,只是它用的是數(shù)據(jù)集 NorthwindWithSprocs的ProductsTableAdapter 和 ProductsDataTable object對(duì)象。與ProductsBLL類使用using NorthwindTableAdapters不同,ProductsBLLWithSprocs類使用的是using NorthwindWithSprocsTableAdapters.同樣的,該類的ProductsDataTable和 ProductsRow對(duì)象使用的是NorthwindWithSprocs命名空間.我們的ProductsBLLWithSprocs class類提供了2種數(shù)據(jù)訪問(wèn)方法GetProducts() 和GetProductByProductID().另外,還有添加、更新、刪除單個(gè)產(chǎn)品的方法.
第七步:在表現(xiàn)層出來(lái)數(shù)據(jù)集NorthwindWithSprocs
此時(shí),我們以及對(duì)數(shù)據(jù)訪問(wèn)層和業(yè)務(wù)邏輯層做了相關(guān)改動(dòng),接下來(lái)我們要?jiǎng)?chuàng)建一個(gè)ASP.NET頁(yè)面調(diào)用BLL的ProductsBLLWithSprocs class類以展示、更新、刪除記錄.
打開AdvancedDAL文件夾里的NewSprocs.aspx頁(yè)面,從工具箱拖一個(gè)GridView控件到頁(yè)面,設(shè)置其ID為Products. 從GridView的智能標(biāo)簽將其綁定到一個(gè)名為ProductsDataSource的ObjectDataSource,設(shè)置其調(diào)用ProductsBLLWithSprocs類.
圖22:設(shè)置ObjectDataSource調(diào)用ProductsBLLWithSprocs類
SELECT標(biāo)簽的下拉列表里有2個(gè)方法,GetProducts()和GetProductByProductID().由于我們將在GridView里顯示所有的產(chǎn)品,所以我們選GetProducts()方法.在UPDATE, INSERT, 和DELETE標(biāo)簽里都只有一個(gè)方法,確保選中它們,點(diǎn)Finish按鈕。
完成設(shè)置后,Visual Studio會(huì)向GridView添加BoundFields列以及一個(gè)CheckBoxField列, 啟用GridView控件的“編輯”和“刪除”功能.
圖23:頁(yè)面包含一個(gè)可以分頁(yè)和排序的GridView控件.
就像在以前的教程里探討過(guò)的一樣,完成ObjectDataSource的設(shè)置后,Visual Studio 會(huì)自動(dòng)的將OldValuesParameterFormatString屬性設(shè)置為“original_{0}”. 為使數(shù)據(jù)修改功能正常工作,要么將該屬性刪除,要么將其設(shè)置為“{0}”.
在我們完成設(shè)置、啟用“編輯”和“刪除”功能、將OldValuesParameterFormatString屬性設(shè)為其默認(rèn)值后,頁(yè)面的聲明代碼看起來(lái)應(yīng)該和下面的差不多:
asp:GridView ID="Products" runat="server" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="ProductsDataSource">
Columns>
asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
asp:BoundField DataField="ProductID" HeaderText="ProductID"
InsertVisible="False" ReadOnly="True"
SortExpression="ProductID" />
asp:BoundField DataField="ProductName" HeaderText="ProductName"
SortExpression="ProductName" />
asp:BoundField DataField="SupplierID" HeaderText="SupplierID"
SortExpression="SupplierID" />
asp:BoundField DataField="CategoryID" HeaderText="CategoryID"
SortExpression="CategoryID" />
asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit"
SortExpression="QuantityPerUnit" />
asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice"
SortExpression="UnitPrice" />
asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock"
SortExpression="UnitsInStock" />
asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder"
SortExpression="UnitsOnOrder" />
asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel"
SortExpression="ReorderLevel" />
asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
SortExpression="Discontinued" />
/Columns>
/asp:GridView>
asp:ObjectDataSource ID="ProductsDataSource" runat="server"
DeleteMethod="DeleteProduct" InsertMethod="AddProduct"
SelectMethod="GetProducts" TypeName="ProductsBLLWithSprocs"
UpdateMethod="UpdateProduct">
DeleteParameters>
asp:Parameter Name="productID" Type="Int32" />
/DeleteParameters>
UpdateParameters>
asp:Parameter Name="productName" Type="String" />
asp:Parameter Name="supplierID" Type="Int32" />
asp:Parameter Name="categoryID" Type="Int32" />
asp:Parameter Name="quantityPerUnit" Type="String" />
asp:Parameter Name="unitPrice" Type="Decimal" />
asp:Parameter Name="unitsInStock" Type="Int16" />
asp:Parameter Name="unitsOnOrder" Type="Int16" />
asp:Parameter Name="reorderLevel" Type="Int16" />
asp:Parameter Name="discontinued" Type="Boolean" />
asp:Parameter Name="productID" Type="Int32" />
/UpdateParameters>
InsertParameters>
asp:Parameter Name="productName" Type="String" />
asp:Parameter Name="supplierID" Type="Int32" />
asp:Parameter Name="categoryID" Type="Int32" />
asp:Parameter Name="quantityPerUnit" Type="String" />
asp:Parameter Name="unitPrice" Type="Decimal" />
asp:Parameter Name="unitsInStock" Type="Int16" />
asp:Parameter Name="unitsOnOrder" Type="Int16" />
asp:Parameter Name="reorderLevel" Type="Int16" />
asp:Parameter Name="discontinued" Type="Boolean" />
/InsertParameters>
/asp:ObjectDataSource>
此時(shí),我們可以對(duì)GridView控件做些修改,比如在編輯界面里使用確認(rèn)控件,在CategoryID 和 SupplierID列放置DropDownList控件,當(dāng)點(diǎn)擊Delete按鈕時(shí)彈出確認(rèn)框等.由于在以前的教程我們探討過(guò)這些主題,我不打算在此多花筆墨。
不管你做沒(méi)做這些改進(jìn),讓我們?cè)跒g覽器里對(duì)頁(yè)面測(cè)試,如圖24所示.在GridView控件里每行都可以編輯和刪除.
圖24:可以通過(guò)GridView對(duì)產(chǎn)品進(jìn)行查看、編輯、刪除
結(jié)語(yǔ):
類型化數(shù)據(jù)集里的TableAdapters可以通過(guò)ad-hoc SQL statement或存儲(chǔ)過(guò)程訪問(wèn)數(shù)據(jù)庫(kù)里的數(shù)據(jù).當(dāng)處理存儲(chǔ)過(guò)程時(shí),我們要么使用現(xiàn)有的存儲(chǔ)過(guò)程,要么使用TableAdapter向?qū)?chuàng)建一個(gè)基于SELECT查詢的新的存儲(chǔ)過(guò)程.在本文,我們考察了如何自動(dòng)的創(chuàng)建一個(gè)存儲(chǔ)過(guò)程.
雖然自動(dòng)創(chuàng)建可以節(jié)省時(shí)間,但是在某些情況下,向?qū)ё詣?dòng)創(chuàng)建的存儲(chǔ)過(guò)程與我們的期望值還是有差距.比如自動(dòng)創(chuàng)建的Products_Update存儲(chǔ)過(guò)程,它包含@Original_ProductID 和 @ProductID這2個(gè)參數(shù),但@Original_ProductID參數(shù)對(duì)我們來(lái)說(shuō)是多余的.
在接下來(lái)的文章,我們將考察TableAdapter使用現(xiàn)有的存儲(chǔ)過(guò)程的情況.
祝編程快樂(lè)!
作者簡(jiǎn)介
本系列教程作者 Scott Mitchell,著有六本ASP/ASP.NET方面的書,是4GuysFromRolla.com的創(chuàng)始人,自1998年以來(lái)一直應(yīng)用 微軟Web技術(shù)。大家可以點(diǎn)擊查看全部教程《[翻譯]Scott Mitchell 的ASP.NET 2.0數(shù)據(jù)教程》,希望對(duì)大家的學(xué)習(xí)ASP.NET有所幫助。
您可能感興趣的文章:- asp.net 結(jié)合mysql存儲(chǔ)過(guò)程進(jìn)行分頁(yè)代碼
- asp.net安全、實(shí)用、簡(jiǎn)單的大容量存儲(chǔ)過(guò)程分頁(yè)
- asp.net 存儲(chǔ)過(guò)程調(diào)用
- asp.net結(jié)合aspnetpager使用SQL2005的存儲(chǔ)過(guò)程分頁(yè)
- asp.net sql存儲(chǔ)過(guò)程
- 在ASP.NET中用存儲(chǔ)過(guò)程執(zhí)行SQL語(yǔ)句
- asp.net利用存儲(chǔ)過(guò)程和div+css實(shí)現(xiàn)分頁(yè)(類似于博客園首頁(yè)分頁(yè))
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十六:在TableAdapters中使用現(xiàn)有的存儲(chǔ)過(guò)程
- 在ASP.NET 2.0中操作數(shù)據(jù)之七十二:調(diào)試存儲(chǔ)過(guò)程
- 在ASP.NET 2.0中操作數(shù)據(jù)之七十三:用Managed Code創(chuàng)建存儲(chǔ)過(guò)程和用戶自定義函數(shù)(上部分)