在SQL SERVER 2005中調用Web Service
原文:Nico Jacobs
出處:U2U nv/sa http://www.u2u.net/Article.aspx?ART=WebServicesinSQL05
改編,翻譯:劉凡 [email protected]
(譯者注:本人根據VS2005 RTM版和SQL SERVER 2005對涉及到Beta環境的步驟說明進行了調整。根據http://blogs.msdn.com/sqlclr/archive/2005/07/25/Vineet.aspx和實際測試,在VS 2005 RTM版中不需要採用手工的方式生成WebService代理類。)
介紹
SQL SERVER2005提供了在.NET中自定義函數、存儲過程、聚合、觸發器以及類型的功能。於是,我們可以在數據庫的函數、存儲過程或類型整合.NET對象方法等。在之前的一篇文章,我們演示瞭如何將正則表達式功能整合到SQL CLR中。在本文中,還是演示這種整合,但是這次是從SQL CLR中調用一個Web Service。我們將使用AdventureWorks這個樣例數據庫,定義一個函數,根據http://www.webservicex.net/CurrencyConvertor.asmx這個Web Service提供的匯率,進行從歐元到美元的外匯轉換。(這個WebService提供的匯率,只用於演示,不能作爲真實金融交易的數據。)
本文沒有詳細的描述所有的細節,讀者應該具有一定的在SQL SERVER2005下開發CLR的基礎。
定義轉換函數
首先,在Visual Studio 2005中,根據開發模板,選擇新建一個C#或者VB.NET的SQL SERVER項目。(注意:在Beta版中,必須在安裝VS2005之前安裝SQL SERVER 2005)
*在一些非正式的VS2005的版本中,直接使用工具引用WebService生成的代理類不能在SQL SERVER CLR中使用。原因是這種代理類生成了不能使用在CLR中的同步方法。具體可參考Vineet Rao 在 blogs.msdn.com中的文章。解決的方法是利用C:/Program Files/Microsoft Visual Studio 8/SDK/v2.0/Bin目錄下的WSDL.exe工具生成老式異步方法的代理類,只用這種類才能在CLR中以External ACCESS的權限加載。如果我們使用“新”的代理類,必須使用Unsafe的安全級別,這種方式應儘量避免。我們可以使用一個XML配置文件來生成需要規範的代理類。創建一個以下內容的XML文件:
<wsdlParameters xmlns='http://microsoft.com/webReference/'>
<language>c#</language>
<protocol>Soap</protocol>
<nologo>true</nologo>
<sharetypes>false</sharetypes>
<webReferenceOptions>
<codeGenerationOptions>properties oldAsync</codeGenerationOptions>
</webReferenceOptions>
</wsdlParameters>
假設保存路徑爲:c:/oldwsdlconfig.xml(如果使用VB.net項目,則替換使用<language>vb</language>元素)。然後,使用以下命令行生成代理類:
C:/Program Files/Microsoft Visual Studio 8/SDK/v2.0/Bin/wsdl.exe /par:c:/ oldwsdlconfig.xml http://www.webservicex.net/CurrencyConvertor.asmx
運行結束後,將生成CurrencyConvertor.cs(或者.vb)文件。在VS中使用“添加現有項”定位到文件的位置後,將CurrencyConvertor.cs(或者.vb)文件增加到現有項目中。同時在項目中增加對System.Web.Services的引用:
下面,我們防止代碼在使用WebService通信中使用序列化和反序列化的過程(創建專門進行序列化的程序集)。在VS中選擇解決方案資源管理器,右鍵選擇屬性。C#和VB.NET的設置方法稍有不同:
C#:
選擇“生成”標籤,在底部選擇“生成序列化程序集”爲“開”。
VB.NET:
選擇“編譯”標籤,點擊“高級”選項,在窗口底部選擇“生成序列化程序集”爲“開”。
同時,在項目屬性窗口中,我們設置權限等級:在數據庫標籤中,設置“權限級別”爲“外部”。這樣,我們聲明這個程序集是“Safe”的,不會作出危害性的過程,外部調用等。但是,它仍然不如聲明爲“Safe”的程序集安全,因爲它進行了網絡通信,訪問硬盤設備等行爲。
最後,可以進行主程序開發工作了:編寫貨幣轉化函數。這個函數輸入sql money類型的參數,乘上歐元對美元的匯率,返回一個sql money類型的數據。編寫的方法:在解決方案資源管理器中的項目上,右鍵選擇新增用戶自定義函數。並編寫如下代碼:
C#
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlMoney EUR2USD(SqlMoney amount)
{
CurrencyConvertor cc = new CurrencyConvertor();
return new SqlMoney(cc.ConversionRate(Currency.EUR,Currency.USD)
* amount.ToDouble());
}
};
VB.NET
Partial Public Class UserDefinedFunctions
<Microsoft.SqlServer.Server.SqlFunction()> _
Public Shared Function EUR2USD(ByVal amount As SqlMoney) As SqlMoney
Dim cc As New CurrencyConvertor()
Return New SqlMoney(amount.ToDouble() * _
cc.ConversionRate(Currency.EUR, Currency.USD))
End Function
End Class
接下來,便可以生成並部署項目了。
在SQL Management Studio調整
現在可以SQL Server 2005 Management Studio嘗試運行代碼。在SQL Server 2005 Management Studio中導航到我們部署代碼的數據庫(比如Adventureworks),可以看到在“可編程行”文件夾下,“程序集”選項中有一個已加載的程序級,並且創建了一個標量函數:
如果這時我們運行函數,會得到一個錯誤。在對象資源管理器中選擇數據庫右鍵打開,新建查詢。在查詢窗口執行以下T-SQL語句:
SELECT dbo.EUR2USD(1)
當我們執行語句的時候,不但不能獲得希望的結果,反而會得到以下錯誤:
Msg 6522, Level 16, State 2, Line 2
A .NET Framework error occurred during execution of user defined routine or aggregate 'EUR2USD':
System.InvalidOperationException: Cannot load dynamically generated serialization assembly. In some hosting environments assembly load functionality is restricted, consider using pre-generated serializer. Please see inner exception for more information. ---> System.IO.FileLoadException: LoadFrom(), LoadFile(), Load(byte[]) and LoadModule() have been disabled by the host.
原因是少部署了一個DLL。當我們查看VS項目的BIN目錄,會發現生成了兩個DLL文件:WebServiceConsume.dll 和 WebServiceConsume.XmlSerializers.dll.後面一個DLL用來在WEB SERVICE調用時候進行序列化和反序列化,它沒有被自動部署到SQL SERVER數據庫中。我們需要進行手工部署:右鍵點擊SQL management studio中對象資源管理器中“程序集”文件夾,選擇“新建程序集”…
在顯示的窗口中瀏覽選擇指定WebServiceConsume.XmlSerializers.dll的位置。你可以不改變權限級別的“Safe”,這個程序集不進行外部通信。選擇確定後,刷新“程序集”文件夾,可以看到剛纔新建的程序集。
再次運行剛纔的T-SQL語句:它已經能夠成功運行了,提供匯率的Web Service能夠被整合到CLR的函數直接調用了。
Web Services和效率
最後叮囑:慎重使用類似的WebService調用。依賴網絡通信的調用,速度較慢。所以如果你要將這些功能處理大數據量的表時,要非常慎重。測試一下,使用下面的查詢,獲取所有的歐元和美元數據(504 rows, AdventureWorks database):
select StandardCost, dbo.EUR2USD(StandardCost) from Production.Product
大概需要4分44秒才能完成查詢,因爲需要調用WebService 504次。然而,我們可以重寫成以下的方式:
declare @rate float
select @rate = dbo.EUR2USD(1)
select StandardCost, StandardCost * @rate from AdventureWorks.Production.Product
這樣,我們只需要調用WebService一次,將匯率存儲到本地變量。除非WebService的結果在執行期間改變了,後一種方法比前者快300多倍。
慎重的使用WebService:如果WebService的調用結果被重複的使用,儘量將結果緩存到本地SQL變量或者臨時表,避免不必要的重複調用。但同時注意,不能使用CLR來緩存數據。因爲程序集被加載到SQL SERVER後,時不允許儲存靜態或者共享狀態對象的,除非你聲明程序集爲“Unsafe”。
Dr. Nico Jacobs
U2U (www.u2u.net)組織的培訓師和顧問. 關注Microsoft SQL Server 和 ADO.NET領域. 可以通過[email protected]與reach Nico聯繫。