還是自己寫的東西順手些,至少啟動時沒有怪叫。。。
下面向大家介紹如何使用VSS管理Oracle中寫的代碼。
一、簡介
Visual SourceSafe(以下簡稱VSS) 是微軟公司推出的一款版本管理軟體,可以很方便的管理原始碼的版本和處理多方協作,目前相似的軟體有很多,而且有很多也是相當不錯的,但是在本文所描述的功能中,VSS足以完成我們要求的任務。
Oracle是一款大型的資料庫軟體,號稱管理著世界上60%的資料庫,在這裡不對其強大的功能進行更多的描述。在Oracle中,可以使用PL/SQL語言編寫函數(Function),預存程序(Procedure),及包(Package),一般情況下,這些程式都是以原始碼的形式儲存於資料庫中(特別的加密情況除外)。
在程式經常需要修改的時候,版本管理顯的特別重要,本人在使用過程中就經常為此事煩惱。以往的作法是在每次修改程式之前或之後,都把預存程序之類的存成一個檔案,在檔案名稱後加一個日期,表示程式的版本,然後放入專用的目錄中,以便曆史追溯,這樣就會造成一個程式會存在大量的檔案,給後期的維護工作帶來很大的麻煩。後來又嘗試把產生的檔案放入VSS中管理,但是使用很煩瑣,需要先存成檔案,然後在VSS用戶端中checkin,如果需要把很多的程式checkin到VSS中,可是個不小的工作量,在使用Oracle11i系統的時候,apps使用者下的package達到3萬多個,原始碼加起來有1G的數量級,如果用上面的工作方式來checkin,恐怕日子不好過了。
因此,為瞭解決以上問題,本人用C#開發了一個專用的程式,暫且命名為“Visual SourceSafe for Oracle”,也即是說,它是專門為Oracle服務的VSS用戶端,如果需要使用此程式,有個前提條件,就是所在的機器必須裝有Oracle的用戶端軟體和VSS的用戶端軟體,因為程式中要用到它們相應的組件。
首先需要看的是如果利用自己的程式向VSS中checkin內容,這是本程式最關鍵的地方,在經過資料尋找後,發現可以利用VSS中的SSAPI.DLL檔案來實現這個功能,這個DLL是VSS提供的專門用於處理VSS事物的對外的API介面,在vs中可以直接引用,vs會自動產生一個託管的類,名字為Interop.SourceSafeTypeLib.dll,在這個API中,有我們所需要的一切的API函數,包括checkin checkout additem等,在後面會有介紹。
說完VSS這邊,要再來說一下Oracle這邊,我們必須把程式從資料庫裡取出來才能寫到VSS中,否則VSS也是無用武之地了。Oracle的Function/Procedure/Package(Package Header/Package Body)全部儲存於視圖USER_SOURCE中,在這個視圖中,幾個欄位的含義如下:
NAME:程式的名字,可以是包的名字,也可以是預存程序的程式
TYPE:程式的類型,如包或預存程序等
LINE:行號,每條記錄只儲存程式的一行的值
TEXT:程式中每行的內容
根據以上結構,要找出一個預存程序的具體內容應該是找到NAME=預存程序名的所有TEXT的欄位的值,並且按LINE欄位來排序,如下所示:
select text from USER_SOURCE where name='XXXX' order by line
至於如何把這些行變成一個字串,所有程式設計語言都應該是差不多的,只要依次把所有行的內容簡單的加起來就行了,當然,不要忘記在每行的中間加一個分行符號,這樣程式才像以前的樣子,否則就都成一行了,難看又難懂。
二、實現
1.登入
在程式實現方面,第一部分就是要製作一個登入介面,這個登入與VSS登入不同,與Oracle登入也不同,因為在同一個登入介面裡,我同時處理了登入Oracle和VSS兩者,介面如:
根據介面上的字面意思,可以很容易知道這個介面如何使用。
在登入程式裡,使用了一個小技巧,就是利用了註冊表,在每次登入成功後,會把當前的資料庫登入使用者名稱,資料庫連接串,VSS登入使用者名稱,VSS設定檔名寫入到註冊表中,下次登入的時候,直接從註冊表中取出,可以提高程式的使用效率。
讀取註冊表的代碼部分如下:
Microsoft.Win32.RegistryKey key =
Microsoft.Win32.Registry.CurrentUser.OpenSubKey(REGISTRY_KEY, false);
if (key != null)
{
txtOracleUserName.Text = key.GetValue("ORACLE_USER_NAME").ToString();
txtOracleDB.Text = key.GetValue("ORACLE_CONNECTION_STRING").ToString();
txtSSUserName.Text = key.GetValue("SS_USER_NAME").ToString();
txtSSDB.Text = key.GetValue("SS_CONNECTION_STRING").ToString();
}
其中REGISTRY_KEY= "SOFTWARE\\VSS4Oracle\\Login",表示在註冊表中儲存的路徑。
登入成功後寫註冊表的代碼如下:
Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(REGISTRY_KEY, true);
if (key == null)
{
key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(REGISTRY_KEY);
}
key.SetValue("ORACLE_USER_NAME",txtOracleUserName.Text);
key.SetValue("ORACLE_CONNECTION_STRING",txtOracleDB.Text);
key.SetValue("SS_USER_NAME",txtSSUserName.Text);
key.SetValue("SS_CONNECTION_STRING",txtSSDB.Text);
本模組只處理簡單的登入,登入後把表單上的各個參數傳給主表單,資料庫使用者的校正和VSS使用者的校正由主表單來完成。為了把參數傳給主表單,需要給本表單增加幾個屬性,用來表示表單上各個檔案框控制項的值,之所以不直接使用控制項的值而增加屬性來表示,是因為控制項預設狀態是私人變數,不提倡把私人變數變成公有變數。屬性如下:
public string OracleUserName
{
get{return txtOracleUserName.Text;}
}
public string OraclePassword
{
get{ return txtOraclePassword.Text;}
}
public string OracleDB
{
get{
if (txtOracleDB.Text == "")
{
return "(local)";
}
else
{
return txtOracleDB.Text;
}
}
}
public string SSUserName
{
get{ return txtSSUserName.Text;}
}
public string SSPassword
{
get{ return txtSSPassword.Text;}
}
public string SSDB
{
get{return txtSSDB.Text;}
}
2.列出VSS中的項目及內容列表
由登入表單得到各種需要的參數後,就轉到主表單,主表單在初始狀態下,最主要的任何是把VSS中相關的項目列出來供使用。
您的VSS中可能會儲存Oracle以外的程式,但是在本程式中,只對我們相關的Oracle程式部分進行顯示,其它內容不做處理,VSS中顯示的項目結構如下:
$/Oracle/串連串名字/Oracle使用者名稱/
Function
Package
Procedure
Sequence
Synonym
Table
Trigger
View
以上幾類在Oracle中都是以可以找到它們的原始碼,如果您需要增加您新的類型,可以直接在後面的程式中進行修改。
主程式的介面如下:
系統在每次登入的時候,都會檢查VSS中是否包括所需要的上述的項目結構,如果不存在,則自動建立。過程如下:
在主表單的代碼中要添加一個新的命名空間的引用:using SourceSafeTypeLib;
然後在其它操作之前要把它VSS的資料庫,代碼如下:
db = new VSSDatabaseClass();
db.Open(ssDB,ssUserName,ssPassword);
其中db是一個私人變數,類型就是VSSDatabaseClass
開啟資料庫後,開始建立所需要的項目,代碼如下:
string root = "$/Oracle/" + oracleDB + "/" + oracleUserName ;
CreateProjectTree(root);
string[] items = new string[8];
items[0] = "Table";
items[1] = "View";
items[2] = "Trigger";
items[3] = "Function";
items[4] = "Procedure";
items[5] = "Package";
items[6] = "Sequence";
items[7] = "Synonym";
VSSItem item = db.get_VSSItem("$/",false);
for(int i = 0; i < items.GetLength(0); i++)
{
try
{item.NewSubproject(root + "/" + items[i],"created");}
catch(Exception ex)
{System.Diagnostics.Debug.WriteLine(ex.Message);}
}
請注意建立的時候用的是try/catch結構,也就是當需要建立的項目是存在的時候,就跳過去直接處理其它的事物,因為我沒有發現db中有一個函數來檢測是否存在一個項目,所以只能用此種變通的方式來處理。
經過上面的建立工作,在VSS中就已經存在了需要的結構了,您可以直接使用VSS的用戶端去查看,如果是第一次使用,那麼每個項目的裡面是沒有任何內容的,當然了,是因為您還沒有進行任何的checkin操作。
在建立了VSS結構後,主表單中有一個TreeView控制項,用來顯示這個結構,代碼如下:
rtbMessages.AppendText("Building Source Safe project hierarchy ... \n");
Application.DoEvents();
Cursor = Cursors.WaitCursor;
tvProject.Nodes.Clear();
try
{
string root = "$/Oracle/" + oracleDB + "/" + oracleUserName;
tvProject.Nodes.Add(root);
VSSItem vssProj = db.get_VSSItem(root , false);
IVSSItems items = vssProj.get_Items(false);
foreach(VSSItem item in items)
{
tvProject.Nodes[0].Nodes.Add(item.Name);
}
tvProject.Nodes[0].Expand();
}
catch(Exception ex)
{
rtbMessages.AppendText("\n\n" + ex.ToString());
}
finally
{
Cursor = Cursors.Default;
rtbMessages.AppendText("\n\n... Done");
}
上述功能只是顯示一個樹的結構,並不包括具體的內容,比如具體有哪些函數,有哪些的預存程序,在上述代碼中還沒有體現。這部分的顯示功能放在左側的樹的after_selected事件中,即選擇任何一個項目,就顯示該項目的具體情況。
根據可以看出,系統中有兩個package,根據表徵圖可以看出兩個package有所不同,第一種表徵圖表示該對象已經儲存於VSS中,而第二個表徵圖表示此對象在VSS中系統還從沒有被簽入過。
3.簽入(checkin)
checkin是指把資料庫中的對象的代碼匯入到VSS系統中,簽入有幾種方式,在對象上點右鍵,或在左側的分類樹中點右鍵都會彈出簽入的對話方塊,作用是相同的,但是作用的範圍有所不同,如果在對象上點簽入,就是只針對所選擇的對象進行簽入(可以是單選也可以是多選),如果在左側的分類樹上點簽入,會整個目錄進行簽入,比如在Package上籤入,會把系統的所有的Package簽入,如果系統中Package較多,而實際更新的並不多,這種作法將很費時間;如果在$/Oracle/(local)/scott這樣的根結點上點右鍵,則會把所有的Function/Procedur/Package/…等所有的對象全部簽入,在一個大系統中,這將是一個非常費時間的過程,要根據實際情況選擇使用,而如果是一個比較小的系統,經常採用這種方式,會把一些丟掉沒有簽入的對象補到VSS中,是一個比較好的辦法。
簽入的的對話方塊我根據我當前的需求,只設計了一個文字框,就是check的comment,如:
用於記錄本次簽入的注釋。
在簽入之前,我們需要得到Oracle對象的具體內容,如Package,我們要得到具體的代碼才可以,具體的取法在上面已經提到。
因為VSS的特性決定,我們在得到代碼後,必鬚生成一個實際的物理檔案,才可以被VSS使用,因為使用StreamWriter對象把所得到的內容產生一個檔案,如下:
string fileName = tempDir + "\\" + contentName;
if (File.Exists(fileName))
{
File.SetAttributes(fileName,FileAttributes.Normal);
File.Delete(fileName);
}
StreamWriter writer = new StreamWriter(fileName,false,System.Text.Encoding.Default);
writer.Write(content);
writer.Close();
在上面的代碼中,contentName表示對象名,也將用作檔案名稱,tempDir可以是一個臨時目錄,也可以是您自己強行指定的一個目錄,content是從DB中產生的對象的實際的代碼。
在Oracle的命名規則中,”$” 這個字元是合法的,而在VSS中,這個字元表示VSS的根路徑,所以變為不合法的字元,也就是如果某個包的名字包括這個字元,將不能正常checkin,在Oralce11i系統中,這個字元被大量的用在package的名字中。為瞭解決這個問題,需要把$字元替換成別的字元,並且在組建檔案的時候,需要作業系統也能正常識別,經過測試,選擇了“@”這個字元,因此上述的代碼改成如下:
string fileName = tempDir + "\\" + contentName.Replace("$","@");
在最後的簽入的步驟中,直接使用VSSItem的checkin方法或Add方法就可以了。