Lotus Domino 与关系数据库的互操作
---- 一. Lotus Domino 与关系数据库的互操作
---- 在项目开发过程中,遇到的第一个棘手的问题是:如何把原先的关系型数据库中的内容全部导入 Domino 中?因为企业现在的所有数据都集中在一个关系型数据库中,因此希望Notes库能与旧的关系库互操作,而不必在数据库转换上浪费更多人力。
---- 此外,由于Lotus Notes是非结构化的数据库,而关系数据库属于结构化数据库,因此要实现两者的互操作或数据导入/导出势必需要一个专门的转换程序,而这个程序一般来说都属于附加品,要么由Lotus提供,要么由数据库厂商提供,如Lotus Notes与Oracle数据库之间的转换就有专门的程序(Pump)。一般在购买Lotus产品的时候Lotus公司不会提供此类产品,如果需要可以单独购买。而对于企业来说,如果企业数据存放在不止一个关系库中,那么就需要购买若干个此类产品,而且还不一定能买的到,因此能否通过Lotus Script编写一个通用的数据库转换程序就成为本次开发的难点之一。
---- 解决这个问题用到了Lotus Domino R5 中提供的三个Lotus Notes 对象类:ODBCConnection(ODBC 连接)、ODBCQuery(ODBC 查询)以及ODBCResultSet(ODBC结果集)。应用这三个类并辅以Lotus Script语言就能实现与关系数据库的互操作问题。
---- 具体解决方法如下:
---- 第一步 在控制面板——>32位ODBC数据源中建立用户数据源Test;
---- 第二步 在Domino R5中新建一个数据库Try,并建立一个空白表单Connection,此表单没有任何内容,然后在表单上创建一个“操作”,起名为“Read”;
---- 第三步 在“Read”操作的编程窗口中选择编程语言为Lotus Script;
---- 第四步 在编程窗口的对象窗口中点击“Option”事件,并写入如下脚本:
---- Uselsx "*lsxodbc" // 使用Lotus Script 扩展对象中的ODBC类
---- 第五步 选中“Declare”事件,在其中写入:
Dim session As NotesSession
Dim db As NotesDataBase
Dim doc As NotesDocument
Dim qry As ODBCQuery
Dim result As ODBCResultSet
Dim con As ODBCConnection
定义程序中使用到的各种对象。
第六步 选中“Click”事件,在其中写入:
Sub Click(Source As Button)
' Set New Value
Set session = New NotesSession
Set con = New ODBCConnection
// 新建ODBCConnection对象实例
Set qry = New ODBCQuery
// 新建ODBCQuery对象实例
Set result = New ODBCResultSet
// 新建ODBCResultSet对象实例
' 取得当前数据库信息
Set db = session.CurrentDataBase
Set doc = New NotesDocument(db)
// 新建文档
doc.form = "connection"
// 新建文档的表单指向connection
Call con.Disconnect()
// 保证con对象当前没有连接其他数据源
If con.ConnectTo("test") Then
// 如果连接成功
Set qry.connection = con
// 将建立好连接的con交给query对象
qry.SQL = "SELECT * FROM Table1"
// SQL 语句
Set result.Query = qry
// 将已经连接上数据源并写好SQL
语句的query对象赋给result对象
Call result.Execute() // 执行SQL语句
Do // 循环直到结果集为空
Call result.NextRow() // 指针指向下一条记录
For i = 1 To result.Numcolumns
// Numcolumns属性记录关系库中的字段个数
field = result.FieldName(i)
// 根据字段的索引值得到字段的名字
value = result.GetValue(field)
// 取得字段值
If Isdate(value) Then
// 对日期字段的特殊处理
If value = Datevalue("0:00:00") Then
value = ""
Else
value = Format(value,"mm-dd-yyyy")
End If
End If
Set item = doc.AppendItemValue(field,value)
// 将关系库中的值写到Notes当前库的当前表单中
Next
Call doc.save(True,True)
// 当一条记录的所有字段都被写入
Notes库后保存此文档
Set db = session.CurrentDataBase
Set doc = New NotesDocument(db) // 新建文档
doc.form = "connection"
Loop Until result.IsEndOfData
Call con.Disconnect() // 断开与数据源的连接
Else
Messagebox("Could not connect to server")
End If
End Sub
---- 最后,保存表单并运行,用鼠标点击Read 操作后,关系数据库中的内容就被取到Notes的文档型数据库中了。
---- 下面简要说明一下上述代码:
***** 关系数据库的字段要于lotus库的名称域对应
---- 1. New。New 关键字是用来创建Notes 对象的。New 后面是要创建的Notes对象及所需的参数。使用New 关键字需要注意的一点是:在Declare 事件中声明Notes对象的时候不能使用New 关键字,否则编译时会出错;但是在Click 事件中可以按 Dim db As New NotesDatabase(“”,“***.NSF”)格式书写。
---- 2. ODBCConnection 对象是与ODBC建立连接的Notes对象,使用它的ConnectTo方法可以与ODBC用户数据源中定义的任何一个数据源建立连接,同时返回一些相关的信息,如:表的个数,表中的字段数等。
---- 3. ODBCQuery 对象是用户编写标准的SQL查询语句的对象,它的connection 属性是指向已经与ODBC数据源建立好连接的ODBCConnection 对象的;它的SQL 属性是存放用户编写的SQL 语句。
---- 4. ODBCResultSet 对象是执行SQL 查询语句之后存放查询结果的,它的Query 属性是指向包含建立起连接(ODBCConnection)的并写好SQL语句的ODBCQuery对象。
---- 5. ODBCResultSet 对象的NextRow 方法的作用是将ODBCResultSet 的记录指针指向下一条记录。因为在取到关系数据库中的记录之后,ODBCResultSet对象的指针是为空的,也就是说它并不自动指向结果集的第一条记录,NextRow方法即实现了将记录指针指向第一条记录的功能,并且在以后的循环过程中,此方法还将继续把指针逐一指向后面的记录,直到最后一条。
---- 6. 由于原先的关系数据库中的字段比较多,所以采用在程序中直接写入字段名的做法并不明智,因此,本程序使用了ODBCResultSet 对象的FieldName(fieldindex)方法来通过字段的编号获得字段名,然后再使用GetValue(fieldname)函数得到域值,最后调用Document类的AppendItemValue(fieldname,value)函数在表单中追加一个名为fieldname的域并赋值。
---- 在把上述三个对象的属性分别设置好以后,调用ODBCResultSet 对象的Execute 方法,就可以从关系库中读取数据了。
---- 但是使用上面的代码在进行实际数据库内容转换的时候,发现Notes 通过ODBC数据源连接关系数据库时,无法识别中文字段名。如果关系数据库的字段是中文名字,那么ODBCResultSet将为空,解决的办法是将关系数据库中的所有字段都改为英文名字。经过反复测试,发现并不是所有的中文字段名Notes都不识别,是同关系库的驱动程序有关,由于本次开发连接的是Foxpro的DBF数据库,使用的是Foxpro2.6数据库驱动,因此无法通过ODBC获取中文字段名,如果连接的关系库是Microsoft SQL Server7.0则可以通过ODBC数据源识别中文字段名。
---- 在实际数据库的转换过程中同时发现的问题还有:该程序执行完一次后不能把关系型数据库中的内容全部取出来。由于需要转换的关系型数据库的结构比较复杂,共有40多个字段,且包括字符、字符串、日期、数字等多种数据类型,同时库中的记录比较多(共有3000多条记录),因此出现这个问题后,有两个猜测,一是和ODBC驱动所设置的系统缓冲有关,一是和Notes开辟的内存空间有关,然而通过试验,最终认为和Notes有关的可能性最大。这是因为首先,Notes对其所有的类都设置了缓存,这其中也包括ODBCResultSet,通过调试Lotus Script脚本并多次单步跟踪脚本的执行情况,发现每次只要执行到同一条数据库记录(例如第3328条记录)时,ODBCResultSet就认为数据集已经到头了,下面的记录就都丢了。于是使用关系数据库软件打开数据库,并将其中的字段减少若干条后,就可以一次读取出全部3000多条记录。至于需要减少多少个字段才能一次读取出全部记录跟原先的关系型数据库的结构有关,需要具体情况具体实验,没有定论。但是可以肯定的一条是Notes本身确实为每个对象类都设置了缓存;并且Notes能根据当前内存的使用情况改变缓存的大小。其次,当用户开机后不是马上运行Lotus Notes而是先运行一个其他的程序,然后再运行Notes时,上面提到的第3328条数据库记录就有所改变,可能变为第3112条记录,也可能变为第3218条记录,这个数字将变得不唯一,具体是多少不得而知,而是与所运行的应用程序有关。
---- 上述代码实现了通过ODBC数据源读取关系数据库中的内容写到Lotus Notes库,同样也可以使用上述方法实现在Lotus Notes 程序通过ODBC 数据源写回到关系数据库的功能,具体实现方法与上面的过程大同小异,因此只在此附上源代码:
(1) Option 事件中:
Uselsx "*lsxodbc"
(2) Clicked 事件中:
Sub Click(Source As Button)
Dim session As NotesSession
Dim db As NotesDataBase
Dim dc As NotesDocumentCollection
定义程序中用到的对象类
Set session = New NotesSession
Set db = session.CurrentDataBase
取到当前数据库的信息
Dim con As New ODBCConnection
Dim qry As ODBCQuery
Dim result As ODBCResultSet
定义ODBC对象类
Set qry = New ODBCQuery
Set result = New ODBCResultSet
If con.ConnectTo("test") Then
Set qry.Connection = con
Set result.Query = qry
qry.SQL = "SELECT * FROM table1"
Call result.Execute()
Set dc = db.AllDocuments
// 得到当前数据库中所有文档的文档集
If dc.Count = 0 Then
// 如果文档集为空,则直接退出
result.Close(DB_CLOSE)
con.Disconnect
Exit Sub
End If
For i = 1 To dc.Count // 循环,
直到所有文档都被写入关系库
Set doc = dc.GetNthDocument(i)
// 获得第i条文档
Call result.AddRow()
// 在关系库中增加一条记录
Forall j In doc.Items
Dim str_name As String
Dim value As Variant
str_name = j.name
If (str_name < > "FORM")
And (str_name < > "$UpdatedBy") Then
// Notes为每个文档都增加了两个特殊的
NotesItem对象用于标识系统信息
value = doc.GetItemValue(str_name)
Call result.SetValue(str_name,value(0))
// 为关系库中的字段赋值
End If End Forall
result.UpdateRow // 更新关系库
Next
result.Close(DB_CLOSE)
con.Disconnect
Else
Messagebox("Could not connect server")
End If
End Sub
---- 一. Lotus Domino 与关系数据库的互操作
---- 在项目开发过程中,遇到的第一个棘手的问题是:如何把原先的关系型数据库中的内容全部导入 Domino 中?因为企业现在的所有数据都集中在一个关系型数据库中,因此希望Notes库能与旧的关系库互操作,而不必在数据库转换上浪费更多人力。
---- 此外,由于Lotus Notes是非结构化的数据库,而关系数据库属于结构化数据库,因此要实现两者的互操作或数据导入/导出势必需要一个专门的转换程序,而这个程序一般来说都属于附加品,要么由Lotus提供,要么由数据库厂商提供,如Lotus Notes与Oracle数据库之间的转换就有专门的程序(Pump)。一般在购买Lotus产品的时候Lotus公司不会提供此类产品,如果需要可以单独购买。而对于企业来说,如果企业数据存放在不止一个关系库中,那么就需要购买若干个此类产品,而且还不一定能买的到,因此能否通过Lotus Script编写一个通用的数据库转换程序就成为本次开发的难点之一。
---- 解决这个问题用到了Lotus Domino R5 中提供的三个Lotus Notes 对象类:ODBCConnection(ODBC 连接)、ODBCQuery(ODBC 查询)以及ODBCResultSet(ODBC结果集)。应用这三个类并辅以Lotus Script语言就能实现与关系数据库的互操作问题。
---- 具体解决方法如下:
---- 第一步 在控制面板——>32位ODBC数据源中建立用户数据源Test;
---- 第二步 在Domino R5中新建一个数据库Try,并建立一个空白表单Connection,此表单没有任何内容,然后在表单上创建一个“操作”,起名为“Read”;
---- 第三步 在“Read”操作的编程窗口中选择编程语言为Lotus Script;
---- 第四步 在编程窗口的对象窗口中点击“Option”事件,并写入如下脚本:
---- Uselsx "*lsxodbc" // 使用Lotus Script 扩展对象中的ODBC类
---- 第五步 选中“Declare”事件,在其中写入:
Dim session As NotesSession
Dim db As NotesDataBase
Dim doc As NotesDocument
Dim qry As ODBCQuery
Dim result As ODBCResultSet
Dim con As ODBCConnection
定义程序中使用到的各种对象。
第六步 选中“Click”事件,在其中写入:
Sub Click(Source As Button)
' Set New Value
Set session = New NotesSession
Set con = New ODBCConnection
// 新建ODBCConnection对象实例
Set qry = New ODBCQuery
// 新建ODBCQuery对象实例
Set result = New ODBCResultSet
// 新建ODBCResultSet对象实例
' 取得当前数据库信息
Set db = session.CurrentDataBase
Set doc = New NotesDocument(db)
// 新建文档
doc.form = "connection"
// 新建文档的表单指向connection
Call con.Disconnect()
// 保证con对象当前没有连接其他数据源
If con.ConnectTo("test") Then
// 如果连接成功
Set qry.connection = con
// 将建立好连接的con交给query对象
qry.SQL = "SELECT * FROM Table1"
// SQL 语句
Set result.Query = qry
// 将已经连接上数据源并写好SQL
语句的query对象赋给result对象
Call result.Execute() // 执行SQL语句
Do // 循环直到结果集为空
Call result.NextRow() // 指针指向下一条记录
For i = 1 To result.Numcolumns
// Numcolumns属性记录关系库中的字段个数
field = result.FieldName(i)
// 根据字段的索引值得到字段的名字
value = result.GetValue(field)
// 取得字段值
If Isdate(value) Then
// 对日期字段的特殊处理
If value = Datevalue("0:00:00") Then
value = ""
Else
value = Format(value,"mm-dd-yyyy")
End If
End If
Set item = doc.AppendItemValue(field,value)
// 将关系库中的值写到Notes当前库的当前表单中
Next
Call doc.save(True,True)
// 当一条记录的所有字段都被写入
Notes库后保存此文档
Set db = session.CurrentDataBase
Set doc = New NotesDocument(db) // 新建文档
doc.form = "connection"
Loop Until result.IsEndOfData
Call con.Disconnect() // 断开与数据源的连接
Else
Messagebox("Could not connect to server")
End If
End Sub
---- 最后,保存表单并运行,用鼠标点击Read 操作后,关系数据库中的内容就被取到Notes的文档型数据库中了。
---- 下面简要说明一下上述代码:
***** 关系数据库的字段要于lotus库的名称域对应
---- 1. New。New 关键字是用来创建Notes 对象的。New 后面是要创建的Notes对象及所需的参数。使用New 关键字需要注意的一点是:在Declare 事件中声明Notes对象的时候不能使用New 关键字,否则编译时会出错;但是在Click 事件中可以按 Dim db As New NotesDatabase(“”,“***.NSF”)格式书写。
---- 2. ODBCConnection 对象是与ODBC建立连接的Notes对象,使用它的ConnectTo方法可以与ODBC用户数据源中定义的任何一个数据源建立连接,同时返回一些相关的信息,如:表的个数,表中的字段数等。
---- 3. ODBCQuery 对象是用户编写标准的SQL查询语句的对象,它的connection 属性是指向已经与ODBC数据源建立好连接的ODBCConnection 对象的;它的SQL 属性是存放用户编写的SQL 语句。
---- 4. ODBCResultSet 对象是执行SQL 查询语句之后存放查询结果的,它的Query 属性是指向包含建立起连接(ODBCConnection)的并写好SQL语句的ODBCQuery对象。
---- 5. ODBCResultSet 对象的NextRow 方法的作用是将ODBCResultSet 的记录指针指向下一条记录。因为在取到关系数据库中的记录之后,ODBCResultSet对象的指针是为空的,也就是说它并不自动指向结果集的第一条记录,NextRow方法即实现了将记录指针指向第一条记录的功能,并且在以后的循环过程中,此方法还将继续把指针逐一指向后面的记录,直到最后一条。
---- 6. 由于原先的关系数据库中的字段比较多,所以采用在程序中直接写入字段名的做法并不明智,因此,本程序使用了ODBCResultSet 对象的FieldName(fieldindex)方法来通过字段的编号获得字段名,然后再使用GetValue(fieldname)函数得到域值,最后调用Document类的AppendItemValue(fieldname,value)函数在表单中追加一个名为fieldname的域并赋值。
---- 在把上述三个对象的属性分别设置好以后,调用ODBCResultSet 对象的Execute 方法,就可以从关系库中读取数据了。
---- 但是使用上面的代码在进行实际数据库内容转换的时候,发现Notes 通过ODBC数据源连接关系数据库时,无法识别中文字段名。如果关系数据库的字段是中文名字,那么ODBCResultSet将为空,解决的办法是将关系数据库中的所有字段都改为英文名字。经过反复测试,发现并不是所有的中文字段名Notes都不识别,是同关系库的驱动程序有关,由于本次开发连接的是Foxpro的DBF数据库,使用的是Foxpro2.6数据库驱动,因此无法通过ODBC获取中文字段名,如果连接的关系库是Microsoft SQL Server7.0则可以通过ODBC数据源识别中文字段名。
---- 在实际数据库的转换过程中同时发现的问题还有:该程序执行完一次后不能把关系型数据库中的内容全部取出来。由于需要转换的关系型数据库的结构比较复杂,共有40多个字段,且包括字符、字符串、日期、数字等多种数据类型,同时库中的记录比较多(共有3000多条记录),因此出现这个问题后,有两个猜测,一是和ODBC驱动所设置的系统缓冲有关,一是和Notes开辟的内存空间有关,然而通过试验,最终认为和Notes有关的可能性最大。这是因为首先,Notes对其所有的类都设置了缓存,这其中也包括ODBCResultSet,通过调试Lotus Script脚本并多次单步跟踪脚本的执行情况,发现每次只要执行到同一条数据库记录(例如第3328条记录)时,ODBCResultSet就认为数据集已经到头了,下面的记录就都丢了。于是使用关系数据库软件打开数据库,并将其中的字段减少若干条后,就可以一次读取出全部3000多条记录。至于需要减少多少个字段才能一次读取出全部记录跟原先的关系型数据库的结构有关,需要具体情况具体实验,没有定论。但是可以肯定的一条是Notes本身确实为每个对象类都设置了缓存;并且Notes能根据当前内存的使用情况改变缓存的大小。其次,当用户开机后不是马上运行Lotus Notes而是先运行一个其他的程序,然后再运行Notes时,上面提到的第3328条数据库记录就有所改变,可能变为第3112条记录,也可能变为第3218条记录,这个数字将变得不唯一,具体是多少不得而知,而是与所运行的应用程序有关。
---- 上述代码实现了通过ODBC数据源读取关系数据库中的内容写到Lotus Notes库,同样也可以使用上述方法实现在Lotus Notes 程序通过ODBC 数据源写回到关系数据库的功能,具体实现方法与上面的过程大同小异,因此只在此附上源代码:
(1) Option 事件中:
Uselsx "*lsxodbc"
(2) Clicked 事件中:
Sub Click(Source As Button)
Dim session As NotesSession
Dim db As NotesDataBase
Dim dc As NotesDocumentCollection
定义程序中用到的对象类
Set session = New NotesSession
Set db = session.CurrentDataBase
取到当前数据库的信息
Dim con As New ODBCConnection
Dim qry As ODBCQuery
Dim result As ODBCResultSet
定义ODBC对象类
Set qry = New ODBCQuery
Set result = New ODBCResultSet
If con.ConnectTo("test") Then
Set qry.Connection = con
Set result.Query = qry
qry.SQL = "SELECT * FROM table1"
Call result.Execute()
Set dc = db.AllDocuments
// 得到当前数据库中所有文档的文档集
If dc.Count = 0 Then
// 如果文档集为空,则直接退出
result.Close(DB_CLOSE)
con.Disconnect
Exit Sub
End If
For i = 1 To dc.Count // 循环,
直到所有文档都被写入关系库
Set doc = dc.GetNthDocument(i)
// 获得第i条文档
Call result.AddRow()
// 在关系库中增加一条记录
Forall j In doc.Items
Dim str_name As String
Dim value As Variant
str_name = j.name
If (str_name < > "FORM")
And (str_name < > "$UpdatedBy") Then
// Notes为每个文档都增加了两个特殊的
NotesItem对象用于标识系统信息
value = doc.GetItemValue(str_name)
Call result.SetValue(str_name,value(0))
// 为关系库中的字段赋值
End If End Forall
result.UpdateRow // 更新关系库
Next
result.Close(DB_CLOSE)
con.Disconnect
Else
Messagebox("Could not connect server")
End If
End Sub