不用第三方之C#自己实现Visual SourceSafe 管理Oracle的程序
还是自己写的东西顺手些,至少启动时没有怪叫。。。
下面向大家介绍如何使用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方法就可以了。
李鸣(aicken)原创 转载注明