5. 返回值
對函數返回值的處理不同於存儲過程返回值的處理,這常常導致混淆。在函數中,經常是返回一個布爾值來表明函數運行的成功與否。
If SomeFunctionName() = True Then
' Function succeeded
但在調用一個存儲過程時,卻不能使用同樣的方法,因為存儲是用Execute方法運行的,同時返回一個記錄集。
Set rsAuthors = cmdAuthors.Execute
如果得不到一個返回值,如何確定是否已正確執行存儲過程?當發生錯誤時,會報告錯誤,這樣就可使用前一章提供的錯誤處理代碼來處理錯誤。但對於一些非致命的邏輯錯誤怎麼辦?
例如,考慮向employee表添加一個新職員的情形。你可能不想防止兩個職員同名的情況,但想注明這個情況。那麼,可以使用一個返回值以表明是否已有同名的職員存在。存儲過程如下:
CREATE PROCEDURE usp_AddEmployee
@Emp_ID Char(9),
@FName Varchar(20),
@Minit Char(1),
@LName Varchar(30),
@Job_ID SmallInt,
@Job_Lvl TinyInt,
@Pub_ID Char(4),
@Hire_Date Datetime
AS
BEGIN
DECLARE @Exists Int -- Return value
-- See if an employee with the same name exists
IF EXISTS(SELECT *
FROM Employee
WHERE FName = @FName
AND MInit = @MInit
AND LName = @LName)
SELECT @Exists = 1
ELSE
SELECT @Exists = 0
INSERT INTO Employee (emp_id, fname, minit, lname,
job_id, job_lvl, pub_id, hire_date)
VALUES (@Emp_Id, @FName, @MInit, @LName, @Job_ID,
@Job_Lvl, @Pub_ID, @Hire_Date)
RETURN @Exists
END
該過程首先檢查是否有同名的職員存在,並據此設定相應的變量Exists,若存在同名,就設為1,否則為0。然後將該職員加到表中,同時把Exists的值作為返回值返回。
注意盡管返回了一個值,但並未將其聲明為存儲過程的參數。
調用該過程的ASP代碼如下:
<!-- #INCLUDE FILE="../include/Connection.ASP" -->
<%
Dim cmdEmployee
Dim lngRecs
Dim lngAdded
Set cmdEmployee = Server.CreateObject("ADODB.Command")
' Set the propertIEs of the command
With cmdEmployee
.ActiveConnection = strConn
.CommandText = "usp_AddEmployee"
.CommandType = adCmdStoredProc
' Create the parameters
' Notice that the return value is the first parameter
.Parameters.Append .CreateParameter ("RETURN_VALUE", adInteger, _
adParamReturnValue)
.Parameters.Append .CreateParameter ("@Emp_id", adChar, adParamInput, 9)
.Parameters.Append .CreateParameter ("@fname", adVarWChar, adParamInput, 20)
.Parameters.Append .CreateParameter ("@minit", adChar, adParamInput, 1)
.Parameters.Append .CreateParameter ("@lname", adVarWChar, adParamInput, 30)
.Parameters.Append .CreateParameter ("@job_id", adSmallInt, adParamInput)
.Parameters.Append .CreateParameter ("@job_lvl", adUnsignedTinyInt, adParamInput)
.Parameters.Append .CreateParameter ("@pub_id", adChar, adParamInput, 4)
.Parameters.Append .CreateParameter ("@hire_date", adDBTimeStamp, _
adParamInput, 8)
' Set the parameter values
.Parameters("@Emp_id") = Request.Form("txtEmpID")
.Parameters("@fname") = Request.Form("txtFirstName")
.Parameters("@minit") = Request.Form("txtInitial")
.Parameters("@lname") = Request.Form("txtLastName")
.Parameters("@job_id") = Request.Form("lstJobs")
.Parameters("@job_lvl") = Request.Form("txtJobLevel")
.Parameters("@pub_id") = Request.Form("lstPublisher")
.Parameters("@hire_date") = Request.Form("txtHireDate")
' Run the stored procedure
.Execute lngRecs, , adExecuteNoRecords
' Extract the return value
lngAdded = .Parameters("RETURN_VALUE")
End With
Response.Write "New employee added.<P>"
If lngAdded = 1 Then
Response.Write "An employee with the same name already exists."
End If
Set cmdEmployee = Nothing
%>
需要重點注意,返回值應當作為集合中第一個參數被創建。即使返回值並不作為一個參數出現在存儲過程中,總是Parameters集合中的第一個Parameters。
因此,特別強調一點:
存儲過程的返回值必須聲明為Parameters集合中第一個參數,同時參數的Direction值必須為adParamReturnValue。
使用返回值
現在定義一個初始窗體,如圖9-3所示:
圖9-3 初始窗體界面
按下Add Employee按鈕會產生如圖9-4所示的顯示:
圖9-4 按下Add Employee按鈕後顯示的界面
再添加同樣的細節(ID號不同)會得到如圖9-5所示的界面:
圖9-5 添加細節後顯示的界面
6. 更新參數
無需輸入所有的參數細節,只需調用Refresh方法,就能讓ADO完成更新。例如,假設已經創建了一個帶有與前面例子相同的參數的過程usp_AddEmployee,並且沒有改變運行的頁面。
With cmdEmployee
.ActiveConnection = strConn
.CommandText = "usp_Addemployee"
.CommandType = adCmdStoredProc
然後調用Refresh方法。
.Parameters.Refresh
這告訴ADO向數據存儲請求每個參數的細節,並創建Parameters集合。然後可以為其賦值。
.Parameters("@Emp_Id") = Request.Form("txtEmpID")
.Parameters("@FName") = Request.Form("txtFirstName")
.Parameters("@MInit") = Request.Form("txtInitial")
.Parameters("@LName") = Request.Form("txtLastName")
.Parameters("@Job_ID") = Request.Form("lstJobs")
.Parameters("@Job_Lvl") = Request.Form("txtJobLevel")
.Parameters("@Pub_ID") = Request.Form("lstPublisher")
.Parameters("@Hire_Date") = Request.Form("txtHireDate")
注意並不需要創建任何參數,包括返回值。
這似乎真是一條捷徑,但應意識到這種方法也造成了性能上的損失,因為ADO必須向提供者查詢以獲得存儲過程的參數細節。盡管如此,這種方法還是很有用的,尤其是在從參數中取出正確的值有困難的時候。
實際上,可以編寫一個小實用程序作為開發工具使用,用來完成更新並建立Append語句,可以將其粘貼到自己的代碼中。它看上去應該與圖9-6所示的GenerateParameters.asp ASP頁面類似。
圖9-6 GenerateParameters.asp ASP頁面
其代碼相當簡單。首先是包含連接符串和另一個ADOX常數文件。
<!-- #INCLUDE FILE="../Include/Connection.ASP" -->
<!-- #INCLUDE FILE="../Include/ADOX.ASP" -->
接下來創建一個窗體,指定目標為PrintParameters.asp ASP頁面。
<FORM NAME="Procedures" METHOD="post" ACTION="PrintParameters.ASP">
Connection String:<BR>
<TEXTAREA NAME="txtConnection" COLS="80" ROWS="5">
<% = strConn %>
</TEXTAREA>
<P>
Stored Procedure:<BR>
<SELECT NAME="lstProcedures">
然後,使用ADOX從SQL Server中得到存儲過程的列表,同時創建一個含有這些存儲過程名字的列表框。
<%
Dim catPubs
Dim procProcedure
' Predefine the quote character
strQuote = Chr(34)
Set catPubs = Server.CreateObject("ADOX.Catalog")
catPubs.ActiveConnection = strConn
For Each procProcedure In catPubs.Procedures
Response.Write "<OPTION VALUE=" & _
strQuote & procProcedure.Name & _
strQuote & ">" & procProcedure.Name
Next
Set procProcedure = Nothing
Set catPubs = Nothing
%>
</SELECT>
<P>
<INPUT TYPE="submit" VALUE="Print Paramaters">
</FORM>
這是一個簡單的窗體,包括一個用於顯示連接字符串的TEXTAREA控件和用於顯示存儲過程名稱的SELECT控件。以前沒有見過的是ADOX,ADOX是數據定義與安全的ADO擴展,可以用來訪問數據存儲的目錄(或是元數據)。
本書不打算介紹ADOX的內容,但其十分簡單。進一步的細節可參見《ADO Programmer's Reference》,Wrox出版社出版,2.1版或2.5版都行。
上面的例子使用了Procedures集合,這個集合包含數據存儲中的所有存儲過程的列表。按下PrintParameters按鈕時,將得到圖9-7所示的顯示:
圖9-7 按下Print Parameters按鈕時顯示的界面
可以簡單地從這裡拷貝參數行到代碼中。在前面使用了一個以前從未見過的包含文件。該文件包含了幾個將ADO常數(例如數據類型、參數方向等)轉換為字符串值的函數:
<!-- #INCLUDE FILE="../Include/Descriptions.ASP" -->
接下來,定義一些變量,提取用戶請求並創建Command對象。
<%
Dim cmdProc
Dim parP
Dim strConnection
Dim strProcedure
Dim strQuote
' Get the connection and procedure name from the user
strQuote = Chr(34)
strConnection = Request.Form("txtConnection")
strProcedure = Request.Form("lstProcedures")
'Update the user
Response.Write "Connecting to <B>" & strConnection & "</B><BR>"
Response.Write "Documenting parameters for <B>" & _
strProcedure & "</B><P><P>"
Set cmdProc = Server.CreateObject("ADODB.Command")
' Set the propertIEs of the command, using the name
' of the procedure that the user selected
With cmdProc
.ActiveConnection = strConnection
.CommandType = adCmdStoredProc
.CommandText = strProcedure
然後使用Refresh方法自動填寫Parameters集合。
.Parameters.Refresh
現在可以遍歷整個集合,寫出包含創建參數所需的細節內容的字符串。
For Each parP In .Parameters
Response.Write ".Parameters.Append & _
"("strQuote & parP.Name & _
strQuote & ", " & _
DataTypeDesc(parP.Type) & ", " & _
ParamDirectionDesc(parP.Direction) & _
", " & _
parP.Size & ")<BR>"
Next
End With
Set cmdProc = Nothing
%>
在Descriptions.ASP包含文件中可以找到函數DataTypeDesc和ParamDirectionDesc。
Descriptions.ASP包含文件以及其他的例子文件可以在Web站點http://www.wrox.com中找到。
這是一個非常簡單的技術,它較好地使用了Refresh方法。
9.3 優化
優化是每個開發人員應該關心的問題。對於數據庫訪問,優化是一個關鍵問題。和其他任務相比,數據的訪問顯得相對慢些。
因為數據訪問的變化是如此之多,以致於幾乎不可能提出一套固定的數據庫操作的優化規則。通常碰到這類問題,經常得到這樣的回答:“這取決於……”,因為這類優化問題取決於准備做什麼。
9.3.1 常用的ADO技巧
盡管優化取決於所執行的任務,但是仍然有一些常用的技巧:
· 僅選擇所需的列。當打開記錄集時,不要自動地使用表名(即SELECT *),除非需要獲得所有的列。使用單獨的列意味著將減少發送到服務器或從服務器取出的數據的數量。即使需要使用全部列,單獨地命名每個列也會獲得最佳的性能,因為服務器不必再解釋這些列是什麼名字。
· 盡可能使用存儲過程。存儲過程是預先編譯的程序,含有一個已經准備好的執行計劃,所以比SQL語句執行得更快。
· 使用存儲過程更改數據。這總是比在記錄集上使用ADO方法執行速度快。
· 除非必需否則不要創建記錄集。運行操作查詢時,要確定加入了adExecuteNoRecords選項,這樣記錄集就不會創建。當僅僅返回一個或兩個字段的單行記錄時(比如ID值),也可以在查詢狀態下使用這種方法。在這種情況下,存儲過程和輸出參數將會更快。
· 使用適當的光標和鎖定模式。如果所做的全部工作是從記錄集中讀取數據,並將其顯示在屏幕上(比如,創建一個表),那麼使用缺省的只能前移的、只讀的記錄集。ADO用來維護記錄和鎖定細節的工作越少,執行的性能就越高。
9.3.2 對象變量
當遍歷記錄集時,一個保證能提高性能的方法是使用對象變量指向集合中的成員。例如,考慮下面的遍歷含有Authors表的記錄集的例子。
While Not rsAuthors.EOF
Response.Write rsAuthors("au_fname") & " " & _
rsAuthors("au_lname") & "<BR>"
rsAuthors.MoveNext
Wend
可以用下面的方法加速代碼執行,同時使其更易於理解。
Set FirstName = rsAuthors("au_fname")
Set LastName = rsAuthors("au_lname")
While Not rsAuthors.EOF
Response.Write FirstName & " " & LastName & "<BR>"
rsAuthors.MoveNext
Wend
這裡使用了兩個變量,並指向記錄集的Fidds集合中的特定字段(記住,Fidds集合是缺省的集合)。因為這裡建立了一個對象的引用,所以可以使用對象變量而不是實際的變量,這意味著腳本引擎的工作減少了,因為在集合中進行索引的次數變少了。
9.3.3 高速緩存大小
高速緩存的大小是指ADO每次從數據存儲中讀取的記錄的數量,缺省為1。這意味著當使用基於服務器的光標時,每當移動到另一條記錄時,必須從數據存儲中提取記錄。舉一個例子,如果增大高速緩存的大小為10,那麼每次讀ADO緩沖區的記錄數將變為10。如果訪問位於高速緩存內的記錄,那麼ADO不需要從數據存儲中取記錄。當訪問位於高速緩存外的記錄時則下一批記錄將讀入到高速緩存中。
通過使用記錄集的CacheSize屬性,可以設置高速緩存的大小。
rsAuthors.CacheSize = 10
可以在記錄集生命期的任何時候改變高速緩存的大小,但新的數量只在提取下一批記錄後才有效。
與許多改進性能的技巧類似,高速緩存沒有通用的最佳大小,因為它隨任務、數據和提供者的不同而改變。但是,從1開始增加高速緩存的大小總是能提高性能。
如果你想看到這一點,可以使用SQL Server profiler並查看使用缺省的高速緩存打開一個記錄集發生的情況,並比較增大高速緩存後發生的情況。增大高速緩存的大小不僅減低了ADO的工作量,同時也降低了SQL Server的工作量。
9.3.4 數據庫設計
不要希望只通過編程來提高對數據的訪問效率,應該同時考慮一下數據庫的設計。這裡並不打算對數據庫設計進行更多的討論,但在使用Web站點數據庫時應考慮以下幾點:
· 實時數據:向用戶顯示數據時,確保數據內容總是最新是十分重要的。以一份產品目錄為例,目錄內容改變的頻率有多快?如果該目錄並非經常改變,那麼不必每次都從數據庫中提取數據。每周一次,或在數據改變時從數據庫產生一個靜態的Html頁面應是一個更好的辦法。
· 索引:如果需要對表進行大量的查詢,而不執行太多的添加數據操作,那麼可以考慮為表建立索引。
· 不規范化:如果站點有兩個不同的目的(數據維護與數據分析),那麼可以考慮采用一些不規范化的表以便有助於數據的分析。可以提供獨立的、完全不規范化的但能正常更新的分析用表,為了改善性能甚至可以將這些分析表移到另一台機器上。
· 數據庫統計:如果使用的是SQL Server 6.x,如果數據被添加或刪附除,那麼應定期更新統計結果。這些統計結果用於產生一個查詢計劃,會影響查詢的運行。請閱讀SQL Books Online中的UPDATE STATISTIC以便了解更詳細的內容。在SQL Server 7.0中這一任務自動完成。
這些都是十分基本的數據庫設計技巧,但若只埋頭於ASP代碼可能不會考慮到這些。