本系列文章建立在以下軟件環境的基礎上:
Windows Server 2003 Enterprise Edition Service Pack 2
Microsoft Visual Studio Team System 2008
Microsoft SQL Server 2008
Microsoft Office Sharepoint Server 2007
背景
先說說為什麼要進行SQL Server和Sharepoint List的數據同步,這是一個實 際項目的應用。
客戶的Sharepoint站點上擁有四個List,相互之間組成了一個具有層次結構的 Organization數據,該數據最終來源於由另外一個系統所使用的數據庫中的一個 表。在項目的前期,開發人員手動從數據庫中將數據導出來,然後導入到 Sharepoint站點已創建好的List中;項目的後期,源數據庫中的數據發生了變化 ,於是開發人員需要手動檢查這些數據的變化,並同步到Sharepoint List中。因 為數據量比較大,這項工作很耗費人力和精力,於是,我們打算在SQL Server中 編寫一個存儲過程,並添加到SQL Server的計劃任務中,讓SQL Server在一個周 期中自動去同步這些數據。因為涉及到Sharepoint站點和List的訪問以及權限的 控制,我們想到了在SQL Server中使用CLR方法來編寫存儲過程並進行部署。以下 是這四個List的結構和數據源的截圖。
Area List
兩個text類型的字段,Title和Area_Code,Title是Area的名稱,Area_Code是 Area的地區編碼。
Region List
Title:text類型,Region的名稱。
Area:Loopup類型,指向Area的ID。
Region_Code:text類型,Region的編碼。
Country List
Title:text類型,Country的名稱。
Region:Lookup類型,指向Region的ID。
Country_Code:text類型,Country的編碼。
CompanyCode List
CompanyName:text類型,Company的名稱。
CompanyCode:Company的編碼。
Country:Lookup類型,指向Country的ID。
數據庫中的源表
將層次關系組合在一個表中,Area_Code和Area_Name對應Area List, Region_Code和RegionName對應Region List,SubRegion_Code和SubRegion_Name 對應Country List,Company_Code和Company_Name對應CompanyCode List。
准備工作
我們事先需要在一個測試環境中進行編碼測試,然後再部署到生產環境上。先 在測試服務器上新建一個空白Sharepoint站點,然後按照上面的描述創建四個 List,再從客戶的服務器上將數據導入到測試用的數據庫中。創建List的時候注 意字段的名稱,重新修改名稱會導致List字段的內部名稱和外部名稱不一致,這 在後面編寫同步程序的時候需要注意一下。
打開Visual Studio,創建一個SQL Server Project工程,取名為 SqlClrSharePointSynchronizer,接下來就開始我們的同步程序編碼啦。
開始
程序主要完成兩部分的工作,先連接到數據源所在的數據庫,取到數據,然後 連接到要進行數據同步的Sharepoint站點,最後調用WSS提供的方法將數據寫到對 應的List中。考慮到所編寫的程序是需要部署到宿主數據庫中才能執行的,首先 要考慮的問題是采用什麼方式執行程序。因為程序所完成的功能相對比較獨立, 不依賴於數據庫中的其它對象(如表、視圖等),比較理想的方式就是存儲過程 和自定義函數,而自定義函數又分為Scale函數和TVF函數兩種(在前面的文章中 已經介紹過),Scale函數適用於對數據的處理,類似於一個數據處理工具函數, 用在這裡顯然不合適,而TVF函數需要指定一個合適的輸出表結構,況且我們的程 序只是完成一個數據同步過程,輸出的信息可能簡單到只有成功或失敗這樣簡單 的文字,所以也不需要TVF函數。另外,自定義函數是必須要有返回值的,這也可 能不是我們所需要的。那麼存儲過程是最合適的選擇了,於是我們先建好一個名 為Synchronizer的類和一個public static方法SyncHierarchy,來表示一個數據 庫的存儲過程。
接下來的工作是要把基礎工作做好,一個DBUtil類,用來處理數據庫連接和獲 取數據,這個在大多數三層結構的ASP.NET程序開發中都有,沒什麼好說的,只是 我在這裡不需要它完成更多的功能,只需要執行SQL語句並返回一個填充好數據的 DataSet即可,其余的方法都被刪掉了。讀者在附件中可以看到這個簡單的類,這 裡我就不再重復貼出代碼了。
重要的是需要在工程中使用WSS提供的方法。我們在工程中引用Sharepoint站 點提供的Web服務,它的地址是http://SiteName/_vti_bin/lists.asmx,其中 SiteName就是Sharepoint站點的地址。將這個Web引用取名為Lists,修改代理類 如下:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SqlServer.Server;
using System.Security.Principal;
using System.Net;
namespace SqlClrSharePointSynchronizer.Lists
{
public partial class Lists
{
public WindowsImpersonationContext impersonationContext;
public Lists(string siteUrl, bool impersonateCaller, IWebProxy proxy)
: this()
{
if (siteUrl.EndsWith(@"/"))
{
siteUrl = siteUrl + "_vti_bin/Lists.asmx";
}
else
{
siteUrl = siteUrl + "/_vti_bin/Lists.asmx";
}
if (impersonateCaller)
{
this.impersonationContext = SqlContext.WindowsIdentity.Impersonate();
}
this.Url = siteUrl;
this.Credentials = CredentialCache.DefaultCredentials;
//this.Credentials = new NetworkCredential ("uid", "pwd", "domain");
if (proxy != null)
{
this.Proxy = proxy;
}
}
protected override void Dispose(bool disposing)
{
if (disposing && impersonationContext != null)
{
impersonationContext.Undo();
}
base.Dispose(disposing);
}
}
}
主要功能是提升程序訪問Sharepoint List時的權限,該權限利用SqlContext 對象通過SQL Server宿主程序的當前登錄帳戶去模擬,記住權限提升之後必須執 行Undo操作,這個是對應的!
核心部分的代碼在SPListUtil類中完成,主要實現調用Web Service和執行更 新SPList的方法。主要使用到了兩個Web Service方法,Lists.GetListItems()和 Lists.UpdateListItems()。有一個需要注意的地方,SPList中有Lookup類型的字 段,這就需要我們在更新數據的時候將這種字段的值寫成1;#Title1;#2;#Title2 的形式,這個需要自己在程序中去處理。還有就是當一次性更新的數據量比較大 時,執行Lists.UpdateListItems()方法時所使用的CAML會被自動截斷,這個我已 經在《使用WSS的Lists.UpdateListItems()方法之被截斷的CAML 》一文中給出了 解決辦法。
具體的代碼讀者可以看我在本文後面給出的附件。我通過四條SQL語句從數據 源表中讀取數據並填充到DataSet中,然後順序將這四部分數據進行轉換並更新到 對應的SPList中,同時輸出Lists.UpdateListItems()方法的返回信息,該信息是 一段XML文本,包含了執行CAML更新數據時的詳細情況,在FormatResult()方法中 進行格式化,將返回信息格式化為我們希望的樣子。下面是我在程序中使用的用 於取數據的SQL語句。
string SELECT_AREA = "Select Distinct AREA_NAME, AREA_CODE From {0}dbo.SECCompanyHierarchy Where AREA_NAME <> ''";
string SELECT_REGION = "Select Distinct REGION_NAME, AREA_CODE, REGION_CODE From {0}dbo.SECCompanyHierarchy Where REGION_NAME <> ''";
string SELECT_COUNTRY = "Select Distinct SUBREGION_NAME, REGION_CODE, SUBREGION_CODE From {0}dbo.SECCompanyHierarchy Where SUBREGION_NAME <> ''";
string SELECT_COMPANYCODE = "Select COMPANY_CODE, COMPANY_NAME, SUBREGION_CODE From {0}dbo.SECCompanyHierarchy Where COMPANY_CODE <> '' And COMPANY_NAME <> ''";
因為考慮到宿主數據庫中可能會使用LinkedServers方式連接客戶數據庫服務 器,所以使用了這種方式的SQL語句。其它的地方應該都很好理解,讀者看源代碼 就OK了。
最後說一下部署。按照我在前面幾篇文章中介紹的部署方式就可以完成dll在 宿主數據庫服務器上的部署,Visual Studio中直接在項目文件上點擊右鍵,點 Deploy就可以自動部署,一般情況下自動部署不會有什麼問題,但是我們總會遇 到將編譯好的dll部署到正式的服務器上的問題,讀者可以看看我在第三篇文章中 給出的那個手動部署的SQL腳本的例子。部署成功後,在SQL Server查詢分析器中 執行,下面是執行過程的截圖。
存儲過程的第一個參數給定了數據同步的目的地址,第二個參數是 LinkedServers的名稱,如果數據庫就在本地的宿主服務器上,則不需要指定第二 個參數。同時Messages窗口中也給出了經過格式化後的執行結果。
代碼示例下載
結語
通過四篇文章介紹了SQL CLR編程的背景、示例、調試和部署,以及實際應用 的例子,希望對正在使用或將來會使用SQL CLR的朋友有所幫助,同時也為自己今 後使用SQL CLR編程開發做一個記錄。