谷口です
Visual Basic 6.0で、なるべくコードの変更を少なくして複数のデータベースに確実に対応できるようにする方法についてです。
方法は色々考えられると思いますが、ここではVBのインターフェース(Interface)継承を用いてみようと思います。オブジェクト指向を完全にサポートしていないVisual Basic 6.0ですが、やれるところまでやってみようというお話です。
使用するDBはSQL Server 2000とOracleです。
テーブルはOracleの、scottにあるempテーブルを用います。
これを、SQL server 2000にインポートしました。
※以下のソースコードはエラー処理などが省略してあり、完全ではありません。
このテーブルのレコードを保存するために、VO(Value Object)を用意します。
' ソースコード(clsEmpVo):クラスモジュール
' Private 変数
' 従業員番号
Private m_lngEmpNo As Long
' 従業員名
Private m_strEname As String
' 以下略
' プロパティ
Public Property Get EMPNO() As Long
EMPNO = m_lngEmpNo
End Property
Public Property Let EMPNO(lngEmpNo As Long)
m_lngEmpNo = lngEmpNo
End Property
Public Property Get Ename() As String
Ename = m_strEname
End Property
Public Property Let Ename(strEname As String)
m_strEname = strEname
End Property
' 以下略(フィールド分同じものを作ります)
次に、データアクセス用インターフェースクラスを定義します
' ソースコード(clsEmpDao) :
クラスモジュール
Public Function SelectEmp(lngEmpNo As Long) As Collection
End Function
Public Function InsertEmp(EmpVo As clsEmpVo) As Boolean
End Function
Public Function UpdateEmp(EmpVo As clsEmpVo) As Boolean
End Function
このクラスのメソッドの中身は実装しません。
次にこれをImplementsしたSQL Server用クラスを作成します。
(ADO接続用オブジェクトは定義済みとします)
' ソースコード(clsEmpSQLServerDao):クラスモジュール
Implements clsEmpDao
Public Function clsEmpDao_SelectEmp(lngEmpNo As Long) As Collection
Dim cmd As New ADODB.Command
Dim result As New Collection
Dim rs As ADODB.Recordset
' Commandの設定
With cmd
.ActiveConnection = adoSQLServerCon
.CommandText = "SELECT * FROM EMP WHERE EMPNO = ?"
.CreateParameter , adInteger, adParamInput
.Parameters(0).Value = lngEmpNo
End With
' SQLを実行して結果セットを取得します
Set rs = cmd.Execute
' Recordsetから、VOへ値を詰めます
Do Until rs.EOF
Dim EmpVo As clsEmpVo
Set EmpVo = New clsEmpVo
' 修正2005/07/08
' ここで Dim EmpVo = New clsEmpVoとやると
' EmpVoが使いまわされ、同じ値になってしまう
EmpVo.EMPNO = lngEmpNo
EmpVo.Ename = nvl(rs.Fields("ENAME"), "")
EmpVo.Job = nvl(rs.Fields("JOB"), "")
EmpVo.Mgr = nvl(rs.Fields("MGR"), 0)
EmpVo.HireDate = nvl(rs.Fields("HireDate"), 0)
EmpVo.Sal = nvl(rs.Fields("SAL"), 0)
EmpVo.Comm = nvl(rs.Fields("COMM"), 0)
EmpVo.DeptNo = nvl(rs.Fields("DEPTNO"), 0)
Call result.Add(EmpVo)
rs.MoveNext
Loop
' 後始末
rs.Close
Set rs = Nothing
' 戻り値(VOのコレクション)の設定
Set clsEmpDao_SelectEmp = result
End Function
Public Function clsEmpDao_InsertEmp(EmpVo As clsEmpVo) As Boolean
' 省略
End Function
Public Function clsEmpDao_UpdateEmp(EmpVo As clsEmpVo) As Boolean
' 省略
End Function
ポイントは先頭のImplements clsEmpDaoと、メソッド名の前にアンダーバーを使いインターフェースクラス名をつけることです。インターフェースクラスで定義されているメソッドは全て実装する必要があります。
同じくOracle用を作成します。ここではoo4oを使用しています。
' ソースコード(clsEmpOracleDao):クラスモジュール
Implements clsEmpDao
Public Function clsEmpDao_SelectEmp(lngEmpNo As Long) As Collection
Dim oraDynaset As Object
Dim result As New Collection
' パラメータのキー(EMPNO)
Const EMPNO As String = "EMPNO"
' パラメータの定義
oraDatabase.Parameters.Add EMPNO, lngEmpNo, ORAPARM_INPUT
oraDatabase.Parameters(EMPNO).serverType = ORATYPE_NUMBER
' Dynaset
Set oraDynaset = oraDatabase.CreateDynaset("SELECT * FROM EMP WHERE EMPNO = :EMPNO", 0&)
' DynasetkaからVOへ値を詰めます
Do Until oraDynaset.EOF
Dim EmpVo As clsEmpVo
Set EmpVo = New clsEmpVo
' 修正2005/07/08
' ここで Dim EmpVo = New clsEmpVoとやると
' EmpVoが使いまわされ、同じ値になってしまう
EmpVo.EMPNO = lngEmpNo
EmpVo.Ename = nvl(oraDynaset.Fields("ENAME"), "")
EmpVo.Job = nvl(oraDynaset.Fields("JOB"), "")
EmpVo.Mgr = nvl(oraDynaset.Fields("MGR"), 0)
EmpVo.HireDate = nvl(oraDynaset.Fields("HireDate"), 0)
EmpVo.Sal = nvl(oraDynaset.Fields("SAL"), 0)
EmpVo.Comm = nvl(oraDynaset.Fields("COMM"), 0)
EmpVo.DeptNo = nvl(oraDynaset.Fields("DEPTNO"), 0)
Call result.Add(EmpVo)
oraDynaset.MoveNextn 1
Loop
' 後始末
oraDatabase.Parameters.Remove EMPNO
oraDynaset.Close
Set oraDynaset = Nothing
' 戻り値(VOのコレクション)の設定
Set clsEmpDao_SelectEmp = result
End Function
Public Function clsEmpDao_InsertEmp(EmpVo As clsEmpVo) As Boolean
' 省略
End Function
Public Function clsEmpDao_UpdateEmp(EmpVo As clsEmpVo) As Boolean
' 省略
End Function
データベース変更時のコード変更を楽にするために次のようなクラスを作ります。
プログラム中でEmpDaoクラスを作成する時は必ずこのクラスを使用するようにします。そうすることにより使用するデータベースをここで一括管理できます(他にもDaoクラスを作った時は全てここに生成関数を作ります)。DB接続のためのオブジェクト(clsEmpSQLServerDaoのadoSQLServerConやclsEmpOracleDaoのoraDatabase)についてはここでは詳しく扱いませんが、clsEmpDaoにプロパティを追加し(Property Set)、わたしてやるのがよいかもしれません。この方法は、この投稿のASP版
http://blogs.sqlpassj.org/masatotaniguchi/archive/2005/07/09/12119.aspx
で扱ってみました。
' ソースコード(clsScottDaoFactory) :クラスモジュール
Public Function EmpDao() As clsEmpDao
Dim Emp As clsEmpDao
' 変更の必要な箇所
Set Emp = New clsEmpSQLServerDao
' Set Emp = New clsEmpOracleDao
Set EmpDao = Emp
End Function
' 他のDaoクラスの例
Public Function DeptDao() As clsDeptDao
' 略
End Function
次はこれらのクラスの使用例です。
' ソースコード(Form1):フォームモジュール
Private Sub Command1_Click()
Dim result As Collection
Dim ScottDaoFactory As New clsScottDAOFactory
Dim EmpDao As clsEmpDao
Dim EmpVo As clsEmpVo
Set EmpDao = ScottDaoFactory.EmpDao
Set result = EmpDao.SelectEmp(7839)
Set EmpVo = result.Item(1)
Text1.Text = EmpVo.Ename
End Sub
従業員番号が'7839'のものを選択する簡単なコードですが、DBを変更する必要が出た時に加える修正は実装クラスさえ作成してあれば1行ですみます。InterfaceクラスclsEmpDaoは入れ物の役目をしていて、入れ物には「何をするか」の定義だけが書かれています。必要に応じて中身を入れ替えるという考え方です。
DBによる違いは全て実装クラスで吸収できます。
MDI子ウィンドウの管理など他にも応用例があります。