意外と簡単!? .NETでOracle −oo4oからODP.NET移行編−

日本オラクル クロスインダストリー統括本部
OracleDirect テクニカルサービスグループ
大田 浩


目次
はじめに
ODP.NETにするメリット
oo4oとODP.NET アーキテクチャの違い
oo4oとODP.NET コネクションの違い
データアクセスの違い
PL/SQL連携方法についての違い
トランザクション制御について
ADOからの移行


 はじめに

「意外と簡単!? .NETでOracle」シリーズは、Microsoft Visual Studio.NETを使用してOracle10g対応アプリケーションをこれから開発されるかた向けに作成しております。.NETからオラクルへの接続にはさまざまな方法が存在しますが、「意外と簡単!? .NETでOracle」シリーズではオラクル社が提供している Oracle Data Provider for .NETを利用しており、開発言語はVisual Basic.NETを使用しております。今回は「oo4o(Oracle Objects for OLE)からODP.NET移行編」ということで、今までVisual Basic(4,5,6)とoo4oを利用してアプリケーションを開発されていたかたむけに、どのようにして既存のアプリケーションを.NET対応するかを説明します。
「意外と簡単!? .NETでOracle」シリーズが.NET開発者でオラクルを利用したい方のシステム構築の一助になれば幸いです。

  「意外と簡単!? .NETでOracle」シリーズは以下の3つの構成を予定しています。
  1. スマートクライアント編
  2. Web アプリケーション編「ASP.NET」
  3. oo4o(Oracle Objects for OLE)からODP.NET移行編(本書)
  「意外と簡単!? .NETでOracle」シリーズの「oo4oからODP.NET移行編」は、以下の7つの内容から構成しております。
  1. ODP.NETにするメリット
  2. oo4oとODP.NET アーキテクチャの違い
  3. oo4oとODP.NET コネクションの違い
  4. データアクセスの違い
  5. PL/SQL連携方法についての違い
  6. トランザクション制御についての違い
  7. ADOからの移行
  「意外と簡単!? .NETでOracle」シリーズにおける開発環境

データベース・サーバー
OS:Microsoft Windows 2000 Professional + SP4
RDBMS:Oracle Database 10g Standard Edition for Windows
アプリケーション・サーバー
OS:Microsoft Windows 2003 Enterprise
APサーバー:Microsoft Internet Information Services 6.0
開発クライアント
OS:Microsoft Windows 2000 Professional + SP4
開発ツール:Microsoft Visual Studio .NET 2003
  Microsoft Visual Studio 6.0

システム構成図


 ODP.NETにするメリット

Oracle Data Provider for .NET(ODP.NET)はMicrosoft .NET環境(以下、.NET)からOracleデータベースへの最適なデータ接続を提供するミドルウェアです。OLE DB .NETやODBC .NETと違い、ODP.NETはデータアクセスのブリッジを経由しないネイティブ設計なので、最も効率的かつ高機能なデータベース接続を提供します。 また、ODP.NETはOracleに特化しているデータプロバイダであり、以下の固有の機能を実装しています。
  • PL/SQLの完全なサポート
  • ネイティブなOracleデータ型のサポート
  • LOB型、REF CURSOR、DATE型など
  • 接続プーリング
  • 配列バインド
  • グローバリゼーション
  • Unicodeの完全サポート
  • トランザクション
  • XML DBのサポート
  • 透過的アプリケーション・フェイルオーバー(TAF)
また、ODP.NETは.NET Frameworkが提供しているAPIとのシームレスな連携が可能ですので、.NET環境ではOracle Objects for OLE(以下、oo4o)に比べ開発生産性が向上します。また、.NET環境からoo4oを使用した場合、Runtime Callable Wrapper(以下、RCW)が生成されますので、その分処理が遅くなります。RCWとは、マネージドコードがCOMサーバーを呼び出す場合のアンマネージとマネージの相違をラッピングする機能になります。

図1 RCWについて

ODP.NETとoo4oの違いをまとめると、以下のようになります。

■ODP.NETとoo4oの違い
ODP.NET oo4o
Oracleネイティブなドライバー COMサーバーである
データ・アクセスにブリッジが入らない RCWによるデータ変換等のオーバヘッドが発生する
Oracle固有の機能のサポート Oracle固有の機能のサポート

メモ:NET環境からoo4oを使用する場合は、Visual Basic.NET(以下 VB.NET)でのみ使用可能です。Visual C#.NET や Visual C++.NET では動作保証されていませんのでご注意ください。


 oo4oとODP.NET アーキテクチャの違い

oo4oとODP.NETではOracleへのデータアクセス方法が大きく異なります。その中でも最も大きな違いは、ADO.NET(ODP.NETはADO.NETに準拠しています)から採用された、非接続型でのデータアクセス方式です。非接続型の特長としては、DataAdapter 経由でメモリ内の DataSet を利用します。したがって、Oracleデータベースとの接続が切れている状態でもデータにアクセスが可能です。

図2 非接続型でのデータアクセス

逆に、接続型とはCommand オブジェクトを利用し、SQLコマンドを直接発行するようになります。したがって、処理が終了するまでOracleとの接続を維持しなければなりません。oo4oには非接続型でのデータアクセスの概念がありませんので、oo4oからODP.NETへデータプロバイダを変更する際には、新たにこの非接続型でのデータアクセス手法を理解する必要があります。非接続型でのデータアクセス手法の詳細は、「3. データアクセスの違い」で説明します。


 oo4oとODP.NET コネクションの違い

アプリケーションがOracleデータベースを使用してSQLを実行するためには、OracleデータベースにSQL文を渡す手段が必要です。このとき、アプリケーションとOracleデータベースを結ぶSQL実行の通信路が必要になります。この通信路のことを「コネクション」と言います。oo4oとODP.NETのコネクションの大きな違いは、以下の2つが挙げられます。
  • コネクションプーリングの動作
  • 非接続型でのデータベースへの自動接続/自動切断
従来のVisual Basicによるクライアント/サーバーアプリケーション(以下、C/S)では、クライアント毎にコネクションが確立されていました。クライアント数が少ない場合には問題ありませんが、多い場合には、コネクション数が増大し、結果として、データベースサーバーに負荷がかかってしまいます。. NETアプリケーションでは、ASP.NETやXML WEBサービスを利用したスマートクライアントシステムのような、Windowsフォームを利用する場合でも、Webサービス経由のスマートクライアントを利用することができ、コネクションプーリングが利用できます。その場合、中間層のアプリケーションサーバーがデータベースへのコネクションをプーリングし、複数のクライアントがプーリングされたコネクションを利用することにより、アプリケーションがコネクションを確立するためにかかる負荷を軽減させることが可能です。そのような機能を「コネクションプール」と言います。しかし、C/Sシステムではクライアント毎にコネクションが確立されるため、コネクションプールを使用したコネクションの一元化が有効になりませんのでご注意ください。

図3 C/Sアプリケーションでのデータアクセス

図4 WEBアプリケーションでのデータアクセス

ADO.NETでのデータベースへの接続はコネクションプールがデフォルトで有効になっており、ADO.NETに準拠したODP.NETも同様にコネクションプールはデフォルトで有効になっております。oo4oでコネクションプールを利用する場合には、明示的にコネクションプールを有効にするためのコーディングが必要になります。具体的には、OraSessionオブジェクトのCreateDatabasePoolメソッドをコールします。

リスト.1 oo4oでのコネクションプールの利用
‘OraSessionClassをオブジェクト化して、OracleInProcServerとの接続を確立する。
Set OraSession = CreateObject("OracleInProcServer.XOraSession")

‘Databaseとの接続を管理するOraDatabaseオブジェクト
OraSession.CreateDatabasePool 2, 40, 200, "ORCL", "SCOTT/TIGER", 0

‘サービス名、ユーザーID、パスワードを指定してOraDatabaseオブジェクトを生成
Set OraDatabase = OraSession.GetDatabaseFromPool(100)

ODP.NETでは、以下のコードのように、Oracleデータベースへの接続に最低限必要な接続文字列情報、User ID/Password/Data Sourceの指定のみで、コネクションプールが有効になります。

リスト.2 ODP.NETでのコネクションプールの利用(デフォルトで有効)
Imports Oracle.DataAccess.Client
Imports Oracle.DataAccess.Types

Public Class fmMainMenu
    Inherits System.Windows.Forms.Form
(略)
    Private Sub DbConnect()
        Dim conn As New OracleConnectio
        conn.ConnectionString = "Data Source=orcl;User id=scott;Password=tiger"
        conn.Open()
    End Sub

コネクションプールで使用する接続文字列属性とデフォルト値は以下のようになっております。

表2 コネクションプールに関する接続文字列属性
接続文字列属性 デフォルト値 説明
Pooling TRUE 接続プーリングを有効または無効にします。
Connection Lifetime 0 接続の最長存続期間(秒)。
Connection Timeout 15 プールから空いた接続を取得するまで待機する最長時間(秒)。
Max Pool Size 100 プール内の最大接続数。
Min Pool Size 1 プール内の最小接続数。
Incr Pool Size 5 プール内のすべての接続が使用されている場合に確立される接続の数を制御します。
Incr Pool Size 5 プール内のすべての接続が使用されている場合に確立される接続の数を制御します。
Decr Pool Size 1 確立されているが使用されていない接続の数が多すぎる場合にクローズされる接続の数を制御します。
Validate Connection FALSE プールから発生した接続の検証の有効化または無効化。

ODP.NETでコネクションプールを明示的に有効にするには、OracleConnectionオブジェクトのConnectionStringプロパティの接続文字列属性で設定します。

リスト.3 ODP.NETでのコネクションプールの利用(デフォルトで有効)
Dim conn As OracleConnection = New OracleConnection
‘コネクションプールを明示的に設定
conn.ConnectionString = "User Id=SCOTT;Password=TIGER; Data Source=ORCL;” & _
                               "Pooling=True;Min Pool Size=2;Max Pool Size=50"
conn.Open()

コネクションプールは、接続プーリング・サービスによって、プールを一意に識別するためのシグネチャとしてConnectionStringを使用して作成されます。シグネチャとしてConnectionStringは大文字・小文字も区別します。例えば、以下のような接続文字列ですと、Data Sourceの指定が小文字と大文字で違うので、それぞれに別々のコネクションプールが作成されてしまいます。

リスト.4 コネクションプールの生成単位
"User Id=scott;Password=tiger;Data Source=orcl"
"User Id=scott;Password=tiger;Data Source=ORCL"

メモ:ConnectionStringプロパティでサポートされる接続文字列属性の一覧は、OTNの「OracleR Data Provider for .NET開発者ガイド」にて詳細に説明してありますので、そちらをご覧下さい。

コネクションプール以外に、Oracleへの接続に関して、ODP.NETはoo4oにはない以下の固有の機能があります。

  tnsname.oraを使用しない接続

接続文字列属性のOracleへの接続文字列には、「Data Source」として、データベースの場所とデータベース・サービスの名前を示す接続記述子を指定します。具体的にはtnsnames.oraファイルに記述されているネット・サービス名です。

リスト.5 TNSNAMES.ORAのネットサービス名の例

ORCL =
  (DESCRIPTION =
    (ADDRESS_LIST =
        (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))
    )
    (CONNECT_DATA =
        (SERVICE_NAME = orcl)
    )
  )


tnsnames.oraファイルは、クライアント毎に配置する必要があり(Webアプリケーションの場合は、IISがクライアントとなる)、多数のクライアントが存在する場合、そのファイルを配布するのも大変ですし、そのファイルに変更が必要になった場合は、さらに大変です。ODP.NETでは、通常であれば、tnsnames.oraファイルに記述する内容をアプリケーション・コード内に記述することが可能です。

リスト.6  tnsname.oraを使用しない接続

Dim conn As New OracleConnection
Dim sb As New System.Text.StringBuilder
sb.Append("User Id=scott; Password=tiger;")
sb.Append("Data Source=(DESCRIPTION = (ADDRESS_LIST = ")
sb.Append("(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)")
sb.Append("(PORT = 1521)))(CONNECT_DATA = (SERVER = DEDICATED)")
sb.Append("(SERVICE_NAME = ORCL)));")
conn.ConnectionString = sb.ToString
conn.Open()
conn.Close()


  オペレーティング・システム認証と特権接続

ConnectionString属性のUser Idを” / “に設定することにより、データベース・ユーザーの認証にWindowsユーザー・ログイン資格証明を使用できます。また、DBA Privilege属性を介してSYSDBA権限またはSYSOPER権限のいずれかを使用してOracleデータベースに接続できます。

リスト.7  オペレーティング・システム認証と特権接続

Dim cnn As New OracleConnection

‘認証にWindowsユーザー・ログイン資格証明を使用し、DBA Privilege属性に「SYSDBA」を指定
cnn.ConnectionString = "User Id=/;Data Source=orcl;DBA Privilege=SYSDBA" 
cnn.Open()

MsgBox("Connect OK!!")
cnn.Close()


  パスワードの期限切れ

Oracleユーザーのパスワードが期限切れだった場合、新しいパスワードで接続をオープンすることが可能です。

リスト.8  パスワードの期限切れ

Dim cnn As New OracleConnection
cnn.ConnectionString = _
"User Id=scott;Password=tiger;Data Source=ora10g"
Try
   cnn.Open()
Catch
   cnn.OpenWithNewPassword("panther")
End Try



 データアクセスの違い

ODP.NETは、ADO.NETに準拠したデータプロバイダであり、ADO.NETと同じように「接続型データアクセス」と「非接続型データアクセス」を利用してOracleデータベースのデータを取得することが可能です。oo4oでは「非接続型データアクセス」という概念がなく、どちらかというと「接続型データアクセス」に近いアクセスモデルになります。oo4oとODP.NETで使用するオブジェクトを比較すると、以下のようになります。

図5 oo4oとODP.NETで使用するオブジェクトの比較

oo4oからODP.NETへ移行した際に、「接続型データアクセス」と「非接続型データアクセス」でコードにどのような違いがあるのかを説明します。

  接続型データアクセスでの比較

oo4oとODP.NETの「接続型データアクセス」で使用するオブジェクトの比較は以下のようになります。

図6 接続型でのoo4oとODP.NETで使用するオブジェクトの比較

上記のオブジェクトを使用したコードをそれぞれ比較してみましょう。oo4oからemp表の値を取得し、デバックウィンドウに結果を表示するコードは以下のようになります。

リスト.9  oo4oでのデータの取得サンプル
Dim OraDynaset As OraDynaset

‘OraDynasetオブジェクトを生成
Set OraDynaset = _
     OraDatabase.CreateDynaset("SELECT empno,ename FROM emp", ORADYN_READONLY)

‘デバックウィンドウに値を表示
Do Until OraDynaset.EOF
     Debug.Print OraDynaset.Fields("empno").Value + " / " + _
          OraDynaset.Fields("ename").Value


     OraDynaset.MoveNext

Loop

上記のコードをODP.NETを使用したコードにすると以下のようになります。

リスト.10  ODP.NETでのデータの取得サンプル(接続型)
Dim conn As New OracleConnection
conn.ConnectionString = _
   "User ID=scott;Password=tiger;Data Source=orcl"
conn.Open()
Dim cmd As New OracleCommand

‘CommandTextにSQLを設定する。
cmd.Connection = conn
cmd.CommandText = "select empno, ename from emp"

‘OracleCommandオブジェクトのExecuteReaderメソッドを実行して、OracleDataReaderオブジェクトを生成
Dim rdr As OracleDataReader
rdr = cmd.ExecuteReader
‘デバックウィンドウに値を表示
Do While rdr.Read
  Debug.WriteLine(CStr(rdr("empno")) + " / " + _
    rdr("ename"))
Loop

‘接続をClose
rdr.Close()
cmd.Close()

以上のように、接続型でのデータアクセスに関しては、oo4oとODP.NETは類似しております。一つ違いがあるとすれば、oo4oでは、SQLを発行した結果セットを「OraDynaset」に直接格納しているのに対して、ODP.NETでは「OracleCommand」を経由してSQLを発行し、結果セットをOracleDataReaderで取得しています。次に、更新処理の違いを比較してみます。

リスト.11  oo4oでのデータの更新サンプル(接続型)
Dim OraSession As Objects
Dim OraDatabase As Object
Set OraSession = CreateObject("OracleInProcServer.XOraSession")
Set OraDatabase = OraSession.OpenDatabase("orcl", "scott/tiger", &H2&)
‘更新SQLの発行
OraDatabase.ExecuteSQL "update emp set ename='scott' where empno=6"

リスト.12  ODP.NETでのデータの更新サンプル
conn.Open()
Dim cmd As New OracleCommand
cmd.Connection = conn
‘更新SQLの発行
cmd.CommandText = "update emp set ename='Bob' where empno=6"
cmd.CommandType = CommandType.Text
cmd.ExecuteNonQuery()

上記のように、ODP.NETはデータの取得/更新時には必ずOracleCommandを使用します。後程説明する、非接続型データアクセス使用時にもOracleCommandの使用は必須になります。一般的にデータを更新する方法に関しては、SQLを直接発行する方法と、結果セットに対してフィールド単位で値を設定し更新する方法の2つがあります。oo4oではOraDynaset使用し、以下のコードのようにフィールド指定でのデータの更新が可能です。

リスト.13  oo4oでのデータの更新サンプル(フィールド指定)
Set OraDatabase = OraSession.OpenDatabase( _
    "orcl", "scott/tiger", dbOption.ORADB_DEFAULT)
sSql = "SELECT * FROM MstCustomer Where CustomerId='" + TextCustomerId.Text + "'"
Set OraDynaset = _
    OraDatabase.CreateDynaset(sSql, ORADYN_DEFAULT)
OraDynaset.Edit
OraDynaset("CustomerID") = TextCustomerId.Text
OraDynaset("CustomerNAME") =TextCustomerName.Text
OraDynaset("EmployeeNAME") = TextEmployeeName.Text
OraDynaset("kana") = TextKana.Text
OraDynaset("job") = TextJob.Text
OraDynaset("postcode") = TextPostCode.Text
OraDynaset("prefectures") = ComboPrefectures.Text
OraDynaset("address") = TextAddress.Text
OraDynaset("tel") = TextTel.TextOraDynaset("fax") = TextFax.Text
OraDynaset("note") = TextNote.Text
OraDynaset.Updates

しかし、ODP.NETの接続型データアクセスで利用するOracleDataReaderは読み取り専用であるため、上記のようなフィールドを指定した更新が出来ません。ODP.NETでは、非接続型データアクセスを使用してフィールドを指定した更新が可能です。

  非接続型データアクセスでの比較

「非接続型データアクセス」で使用するODP.NETのオブジェクトは以下のようになります。

図6 接続型でのoo4oとODP.NETで使用するオブジェクトの比較

「OracleDataAdapter」と「DataSet」オブジェクトは、非接続型データアクセスを行う場合の固有のオブジェクトになります。oo4oでは非接続型データアクセスの概念がないので、「OracleDataAdapter」と「DataSet」同様のオブジェクトはありません。では、実際にODP.NETを使用した「非接続型データアクセス」のコードを見てみましょう。「OracleDataAdapter」と「DataSet」オブジェクトは、非接続型データアクセスを行う場合の固有のオブジェクトになります。oo4oでは非接続型データアクセスの概念がないので、「OracleDataAdapter」と「DataSet」同様のオブジェクトはありません。では、実際にODP.NETを使用した「非接続型データアクセス」のコードを見てみましょう。

リスト.14  ODP.NETでのデータの取得サンプル(非接続型)

Dim cnn As New OracleConnection
Dim cmd As New OracleCommand
Dim dsList As New DataSet
Dim iCnt As Integer

cnn.ConnectionString = _
      "user id=scott;password=tiger;data source=orcl"
cmd.Connection = cnn

‘DataAdapterを使用して、結果セットをDataSetに格納
cmd.CommandText = "Select * from emp"
Dim adp As New OracleDataAdapter(cmd)
adp.Fill(dsList, "EmpList")

‘DataSetの内容をデバックウィンドウに表示
With dsList.Tables("EmpList")
    For iCnt = 0 To .Rows.Count - 1
      Debug.WriteLine(CStr(.Rows(iCnt).Item("empno")) + " / " + _
        .Rows(iCnt).Item("ename"))
  Next
End With

上記のコードは非接続型データアクセスでテーブルの情報を取得して、デバックウィンドウに表示するコードになります。DataSetに結果セットを格納するには、OracleDataAdapterのFillメソッドを使用します。逆に、DataSetの内容をOracleデータベースに書き戻す作業時にもOracleDataAdapterのUpdateメソッドを使用します。

図7 非接続型でのデータの取得/更新

「リスト.14 ODP.NETでのデータの取得サンプル(非接続型)」のコードを見ると、コネクションのオープンとクローズをおこなっていないのがわかると思います。非接続型では、OracleDataAdapterのFillメソッド、もしくはUpdateメソッドを呼び出したときに自動的にコネクションを確立し、処理が終了したら自動的にコネクションを切断します。DataSetへ値を格納後は、既にOracleデータベースとのコネクションが切れた状態になっています。このように、SQLを発行する瞬間にのみデータベースへ接続し、後の作業はデータベースへの接続が切断された状態でおこないます。このようなアクセス手法によることから、非接続型と呼ばれています。非接続型は、データベースへの接続時間を最小限にし、その結果、データベースサーバーの負荷を下げることが可能になっています。DataSetに格納された値は、OracleDataAdapterのUpdateメソッドを使用し、Oracleデータベースに更新された値を書き戻します。Updateメソッドを実行すると、OracleDataAdapterのUpdateCommand, DeleteCommand, InsertCommandに設定されたSQLが実行されます。

図8 OracleDataAdapterのUpdateメソッドをコールしたときの動作

OracleDataAdapterのUpdateCommand、DeleteCommand、InsertCommandそれぞれのSQLは個別に設定することも可能ですが、OracleCommandBuilderを使用し、SQLを自動生成することが可能です。

リスト.15  ODP.NETでのデータの更新サンプル(非接続型)
Dim CustRow As DataRow
If dsCustomer.Tables("MstCustomer").Rows.Count = 0 Then
    CustRow = dsCustomer.Tables("MstCustomer").NewRow()
Else
    CustRow = dsCustomer.Tables("MstCustomer").Rows(0)
End If
CustRow.Item("CustomerId") = TextBoxID.Text
CustRow.Item("CustomerNAME") = TextBoxCompanyName.Text 
    〜 以下略 〜
If CustRow.RowState = DataRowState.Detached Then _
    dsCustomer.Tables("MstCustomer").Rows.Add(CustRow)
da.Update(dsCustomer.Tables("MstCustomer"))

上記のコードではカラム名を文字列で指定しており、データ型の復元もキャストにより行われているため、実行時エラーが発生する可能性があります。これを回避するためには、型付データセットというのを使用します。型付データセットとはテーブル名や列名などの定義情報を事前に取り込むことにより作成されるデータセットになります。型付データセットを利用すると、コードの可読性が向上しますし、インテリセンスによるコード補完も行われるため、コーディングミスも軽減されます。また、コンパイル時の名前チェックを有効化することも可能です。 形なしデータセットと型付データセットのコードを比較すると、以下のようになります。

リスト.16 形なしデータセットを使用したコード
Dim strCustName As String = _
    dsCustomer.Tables("MstCustomer").Rows(0).Item(“CustomerName”)

リスト.17 形付データセットを使用したコード
Dim strCustName As String = dsCustomer.MstCustomer(0).CustomerName

型付データセットの利用には、データセット定義ファイル(以下、xsdファイル)を作成する必要があります。xsdファイルは「Oracle Developer Tools for Visual Studio .NET」(以下、ODT)を使用すると簡単に作成できます。ODTのダウンロード/インストールについては、Oracle Technology Network(以下、OTN)の以下の 「.NET Developer Center」に情報が掲載されておりますので、そちらをご覧ください。以下にODTを使用してxsdファイルを作成する方法について説明します。

1.   xsdファイルの新規作
  Visual Studio .NETから新規のプロジェクトファイルを作成し、プロジェクトファイルを右クリック−>追加−>新しい項目の追加をクリックしてください。
 
 
「新しい項目の追加」ウィンドウが表示されるので、「データセット」をクリックし、「開く」ボタンをクリックします。
 
2.   xsdファイルの作成
「Oracleエクスプローラ」から対象テーブルを選択し、新規に作成したxsdファイルのデザインウィンドウにドラッグ&ドロップすると、xsdファイルが自動的に作成されます。

3.   型付データセットの作成
ツールボックスよりデータセットを選択し、フォームに貼り付けると、以下のようにデータセットの追加ウィザードが表示されます。
  上記の画面で、「型指定されたデータセット(T)」を選択し、先ほど作成したxsdファイルを選択後、「OK」ボタンをクリックすると、型付データセットが作成されます。
 
メモ:型付データセットの作成方法として、上記で説明した方法とは別の作成方法があります。「Oracleエクスプローラ」から対象テーブルをフォームに直接貼り付けると、OracleDataAdapterが自動生成されます。自動生成されたOracleDataAdapterを右クリックし、「プロパティ」ウィンドウから「DataSetを生成」をクリックすると、型付データセットが自動生成されます。


 PL/SQL連携方法についての違い

oo4oとODP.NETでのPL/SQL連携の違いについて見てみましょう。まずは、以下のSQLを実行し、PL/SQLパッケージを作成します。

リスト.18 サンプルパッケージの作成
CREATE OR REPLACE PACKAGE SCOTT.pkg_ref AS
  CURSOR c1 IS SELECT ename FROM emp;
  TYPE empCur IS REF CURSOR RETURN c1%ROWTYPE;
  PROCEDURE GetEmpData(
    indeptno IN NUMBER,
    EmpCursor in out empCur );
END;

CREATE OR REPLACE PACKAGE BODY SCOTT.pkg_ref AS
    PROCEDURE GetEmpData(indeptno IN NUMBER,
        EmpCursor in out empCur ) IS
             BEGIN
               OPEN EmpCursor FOR
                 SELECT ename FROM emp WHERE deptno=indeptno;
             END GetEmpData;
END pkg_ref;


上記のPL/SQLパッケージのGetEmpDataファンクションは、emp表のdeptno列を引数で取得し、該当する結果セットをRef Cursorで取得しております。次にoo4oから上記のPL/SQLパッケージのGetEmpDataファンクションをコールするコードは以下のようになります。

リスト.19 oo4oからストアドプロシージャをコールし、Ref Cursorを取得するコード
Dim sSql As String

Set OraDatabase = OraSession.OpenDatabase( _
  "orcl", "scott/tiger", dbOption.ORADB_DEFAULT)
‘ deptnoをパラメータで設定
OraDatabase.Parameters.Add "DEPTNO", 10, ORAPARM_INPUT
OraDatabase.Parameters("DEPTNO").serverType = ORATYPE_NUMBER
‘GetEmpDataファンクションをコール
sSql = "Begin pkg_ref.GetEmpData(:DEPTNO, :EmpCursor); end;"
‘Ref Cursorの結果セットを取得
Set OraDynaset = OraDatabase.CreatePlsqlDynaset(sSql, "EmpCursor", 0&)‘
結果をデバックウィンドウに表示
Do While Not OraDynaset.EOF
    Debug.Print OraDynaset("ename")

    OraDynaset.MoveNext

Loop

ODP.NETの接続型でのコードは以下のようになります。

リスト.20 ODP.NETからストアドプロシージャをコールし、Ref Cursorを取得するコード(接続型)
Dim sSql As String

Dim conn As New OracleConnection("User Id=Scott;Password=Tiger;Data Source=orcl")
conn.Open()
‘GetEmpDataファンクションをコールするSQLをOracleCommandに設定
Dim cmd As New OracleCommand("pkg_ref.GetEmpData", conn)
cmd.CommandType = CommandType.StoredProcedure
‘ deptnoをパラメータで設定
cmd.Parameters.Add("DEPTNO", 10)
cmd.Parameters.Add("empCursor", OracleDbType.RefCursor, ParameterDirection.Output)
‘GetEmpDataファンクションをコール
cmd.ExecuteNonQuery()
‘Ref Cursorの結果セットを取得
Dim dr1 As OracleDataReader = _
            CType(cmd.Parameters(1).Value, OracleRefCursor).GetDataReader
‘ 結果をデバックウィンドウに表示
Do While dr1.Read
  Debug.WriteLine(dr1("ename"))
Loop

Ref Cursorの結果セットの取得は、非接続型でのアクセスでも可能です。実際のコードは以下のようになります。

リスト.21 ODP.NETからストアドプロシージャをコールし、Ref Cursorを取得するコード(非接続型)
Dim conn As New OracleConnection("User Id=Scott;Password=Tiger;Data Source=orcl")
‘GetEmpDataファンクションをコールするSQLをOracleCommandに設定
Dim cmd As New OracleCommand("pkg_ref.GetEmpData", conn)
cmd.CommandType = CommandType.StoredProcedure
‘Ref Cursorの結果セットを取得
cmd.Parameters.Add("DEPTNO", 10)
cmd.Parameters.Add("empCursor", OracleDbType.RefCursor, ParameterDirection.Output)

' GetEmpDataファンクションをコールし、結果をDataSetに格納
Dim dsData As New DataSet
Dim da As New OracleDataAdapter(cmd)
da.Fill(dsData, "data")

'Gridへ表示
DataGridEmp.SetDataBinding(dsData, "data")

次に、複数のRef Cursorを取得するコードを比較してみます。実行を確認するために、テスト用のパッケージを作成します。

リスト.22 複数のRef Cursorを返すパッケージの作成
CREATE OR REPLACE PACKAGE SCOTT.pkg_ref2 AS
   CURSOR c1 IS SELECT * FROM emp;
   CURSOR c2 IS SELECT * FROM dept;
   TYPE empCur IS REF CURSOR RETURN c1%ROWTYPE;
   TYPE deptCur IS REF CURSOR RETURN c2%ROWTYPE;
   PROCEDURE GetEmpDeptData(
      EmpCursor in out empCur,
      DeptCursor in out deptCur
);
END;


CREATE OR REPLACE PACKAGE BODY SCOTT.pkg_ref2 AS
   PROCEDURE GetEmpDeptData(
      EmpCursor in out empCur,
      DeptCursor in out deptCur) IS
         BEGIN
            OPEN EmpCursor FOR SELECT * FROM emp;
            OPEN DeptCursor FOR SELECT * FROM dept;
      END GetEmpDeptData;
END pkg_ref2;

上記のPL/SQLパッケージのGetEmpDeptDataファンクションは、emp表とdept表の2つの結果セットを返すコードになります。oo4oで複数のRef Cursorの結果セットを取得するコードは以下のようになります。

リスト.23 oo4oから複数のRef Cursorを取得するコード
Dim sSql As String

Dim OraSession As New OraSessionClass
Dim OraDatabase As OraDatabase

Set OraDatabase = OraSession.OpenDatabase( _
   "orcl", "scott/tiger", dbOption.ORADB_DEFAULT)
OraDatabase.Parameters.Add "EmpCursor", 0, ORAPARM_OUTPUT
OraDatabase.Parameters("EmpCursor").serverType = ORATYPE_CURSOR
OraDatabase.Parameters.Add "DeptCursor", 0, ORAPARM_OUTPUT
OraDatabase.Parameters("DeptCursor").serverType = ORATYPE_CURSOR
sSql = "Begin pkg_ref2.GetEmpDeptData(:EmpCursor,:DeptCursor); end;"
Set OraSqlStmt = OraDatabase.CreateSql(sSql, ORASQL_FAILEXEC)

Set EmpDynaset = OraDatabase.Parameters("EmpCursor").Value
Set DeptDynaset = OraDatabase.Parameters("DeptCursor").Value

Do While Not EmpDynaset.EOF
   Debug.Print EmpDynaset("ename")


    EmpDynaset.MoveNext
Loop

Do While Not DeptDynaset.EOF
     Debug.Print DeptDynaset("dname")


     DeptDynaset.MoveNext
Loop

OraDatabase.Parameters.Remove ("EmpCursor")
OraDatabase.Parameters.Remove ("DeptCursor")

同様に、ODP.NETの接続型で複数のRef Cursorの結果を取得してみます。

リスト.24 ODPから複数のRef Cursorを取得するコード(接続型)
Dim conn As New OracleConnection("User Id=Scott;Password=Tiger;Data Source=orcl")
Dim cmd As New OracleCommand("pkg_ref2.GetEmpDeptData", conn)
cmd.CommandType = CommandType.StoredProcedure

'REF CURSORパラメータのバインド
cmd.Parameters.Add("EmpCursor", OracleDbType.RefCursor, ParameterDirection.Output)
cmd.Parameters.Add("DeptCursor", OracleDbType.RefCursor, ParameterDirection.Output)
cmd.ExecuteNonQuery()

'SQL文の実行とRef Cursorの使用
Dim dr1 As OracleDataReader = _
   CType(cmd.Parameters(0).Value, OracleRefCursor).GetDataReader
Dim dr2 As OracleDataReader = _
   CType(cmd.Parameters(1).Value, OracleRefCursor).GetDataReader

'RefCursorの内容をデバックウィンドウに表示
Do While dr1.Read
   Debug.WriteLine(dr1("ename"))
Loop
Do While dr2.Read
   Debug.WriteLine(dr2("dname"))

Loop

非接続型でも同様に複数のRef Cursorを取得可能です。

リスト.25 ODP.NETから複数のRef Cursorを取得するコード(非接続型)
Dim conn As New OracleConnection("User Id=Scott;Password=Tiger;Data Source=orcl")
Dim cmd As New OracleCommand("pkg_ref2.GetEmpDeptData", conn)
cmd.CommandType = CommandType.StoredProcedure

'REF CURSORパラメータのバインド
cmd.Parameters.Add("EmpCursor", OracleDbType.RefCursor, ParameterDirection.Output)
cmd.Parameters.Add("DeptCursor", OracleDbType.RefCursor, ParameterDirection.Output)

'SQL文の実行とRef Cursorの使用
Dim dsData As New DataSet
Dim da As New OracleDataAdapter(cmd)
da.Fill(dsData, "data")'

Gridへ表示
DataGridEmp.SetDataBinding(dsData, "data")
DataGridDept.SetDataBinding(dsData, "data1")


 トランザクション制御について

oo4oとODP.NETのトランザクション制御の違いについて見ていきましょう。まずは、oo4oでのトランザクション制御のコードについて説明します。

リスト.26 oo4oからのトランザクション制御
'---データベースとの接続を開く
Dim OraSession As New OraSessionClass
Dim OraDatabase As OraDatabase
Set OraDatabase = OraSession.OpenDatabase( _
      "orcl", "scott/tiger", dbOption.ORADB_DEFAULT)
'トランザクションの開始
OraSession.BeginTrans
OraDatabase.ExecuteSQL "insert into emp(empno,ename) values(6,'Michel')"
OraSession.CommitTrans

oo4oでは、OraSessionオブジェクトに対して、トランザクションの制御を行います。ODP.NETでは、OracleConnectionオブジェクトから、OracleConnectionのBeginTransactionメソッドを実行し、ローカル・トランザクションを開始します。

リスト.27 ODP.NETからのトランザクション制御
'---データベースとの接続を開く
cnn.Open()
Dim cmd As New OracleCommand
cmd.Connection = cnn
'トランザクションの開始
Dim txn As OracleTransaction = cnn.BeginTransaction()
cmd.CommandText = "insert into emp(empno,ename) values(6,'Michel')"
cmd.CommandType = CommandType.Text
cmd.ExecuteNonQuery()
txn.Commit()

また、ODP.NETでは以下のように「SavePoint」を指定して、トランザクション内にセーブポイントを作成することが可能です。

リスト.28 ODP.NETからのトランザクション制御(SavePointの利用)
Dim cnn As New OracleConnection

cnn.ConnectionString = "user id=scott;password=tiger;data source=orcl"
cnn.Open()
'トランザクションの開始
Dim txn As OracleTransaction = cnn.BeginTransaction()
Dim strSQL1 As String = "INSERT INTO emp (empno, ename) VALUES (1,'Employee1')"
Dim myCmd As New OracleCommand(strSQL1, cnn)
Dim res As Integer = myCmd.ExecuteNonQuery()
‘SavePoint ‘a’ でトランザクションを保存
txn.Save("a")

Dim strSQL2 As String = "INSERT INTO emp (empno, ename) VALUES (2,'Employee2')"
Dim myCmd2 As New OracleCommand(strSQL2, cnn)
Dim res2 As Integer = myCmd2.ExecuteNonQuery()
‘SavePoint ‘b’ でトランザクションを保存
txn.Save("b")

SavePoint ‘a’までロールバックしコミット
txn.Rollback("a")
txn.Commit()

上記のコードでは、コード内でトランザクションの制御をおこなっています。その他に、COM+サービスを利用した自動トランザクション機能を利用し、トランザクションの制御をおこなうことも可能です。oo4oを利用している場合、データアクセス部分をCOMコンポーネントとして作成し、コンポーネントサービスでCOMコンポーネントの登録を行い、COMコンポーネント単位でトランザクションの管理をおこないます。

 
  図10 コンポーネントサービス画面
 
登録されたコンポーネントは、コンポーネントサービスで、トランザクションサポートの設定が可能です。
 
  図11 コンポーネントサービスでのトランザクションサポートの設定

ODP.NETでも同様にCOM+サービスを利用した自動トランザクションの制御は可能です。ODP.NETからCOM+を利用するには、Oracle Services for Microsoft Transaction Server(OraMTS)がインストールされ、適切に設定されている必要があります。COM+自体のトランザクション機能については、Microsoft社のCOM+に依存する内容であるため、Microsoft社のMSDNサイト、「.NETでCOM+ サービスを使用する」などを参照してください。

メモ:XML WEBサービスとCOM+サービスを利用した自動トランザクションについては、意外と簡単!?「.NETでOracle」のスマートクライアント編を参照してください。


 ADOからの移行

Visual Basic 6.0(以下、VB)からOracleデータベースへの接続ミドルウェアとして、oo4o以外にもADOを選択されている方も多いと思います。ここでは、VBでADOを使い Oracleデータベースにアクセスするアプリケーションを、どのように .NET 化すればよいか説明します。ADOとODP.NETで使用するオブジェクトは以下のようになります。

図11 ADOとODP.NETで使用するオブジェクトの比較

ADOでは、以下のコードのようにRecordSetを開いて、Movenextメソッドでシーケンシャルに読み込みながら処理を行うプログラミングスタイルが一般的だと思います。

リスト.29 ADOのRecordSetを利用したサンプルコード
Do Until RecordSet.Eof
   〜 データアクセス 〜
   RecordSet.Movenext
Loop

以上のコードですと、ループ処理の間はデータベースと接続された状態になっています。ODP.NETのOracleDataReaderは、ADOのRecordSetのReadOnly , FowardOnlyオプションで開いた状態に似ています。. NET環境からのデータアクセスは非接続型でのデータアクセスが主流になりますので、ADOからODP.NETへの移行も、oo4oからODP.NETの移行と同様に、非接続型でのデータアクセス手法を習得する必要があります。OracleDataAdapterを経由した、DataSetへのデータの格納と、DataSetからデータベースへのデータの書き戻しの概念を学ぶ必要があります。

図12 非接続型でのデータアクセス

しかし、既存のADOを利用して作成されたアプリケーションをODP.NETに変更するには、かなりのコードの修正が必要となります。とりあえず、最も簡単にADOを使用して作成されたアプリケーションを.NET化する方法として、Visual Studio .NETに付属している、アップグレードウィザードを使用する方法があります。アップグレードウィザードとは、Visual Basic 6.0 プロジェクトを Visual Basic .NET プロジェクトにアップグレードするツールになります。コマンドラインから、Vbupgrade.exe の引数にプロジェクトを指定することでアップグレードできますし、Visual Basic 6.0 のプロジェクトを Visual Studio .NET で開くと、自動的にウィザードが起動し、プロジェクトをアップグレードすることもできます。

図13 アップグレード ウィザードの起動

実際に、アップグレードウィザードを使用してVisual Basic 6.0 プロジェクトを Visual Basic .NET プロジェクトに変換してみましょう。まずは、Visual Basic 6.0で作成された移行元のアプリケーションを作成します。Oracleデータベースにアクセスし、Visual Besic 6.0 の DataGrid コントロールにemp表の内容を表示するアプリケーションのコードは以下のようになります。

リスト.30 アップグレードウィザードを使用して、.NET化したコード
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset

Set conn = New ADODB.Connection
conn.CursorLocation = adUseClient
conn.ConnectionString = _
      "Provider=OraOLEDB.Oracle.1;User ID=scott;Password=tiger;Data Source=orcl;"
conn.Open
Set rs = conn.Execute("SELECT * FROM emp", , adCmdText)
Set Me.DataGrid1.DataSource = rs


上記のVBで作成されたアプリケーションのプロジェクトファイルをVisual Studio .NETで開くと、アップグレードウィザードが起動し、プロジェクトをVB.NETへアップグレードできます。実際にアップグレードされたコードは以下のようになります。

リスト.31 アップグレードウィザードを使用して、.NET化したコード
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset

conn = New ADODB.Connection
conn.CursorLocation = ADODB.CursorLocationEnum.adUseClient
conn.ConnectionString = _
     Provider=OraOLEDB.Oracle.1;User ID=scott;Password=tiger;Data Source=orcl;"
conn.Open()
rs = conn.Execute("SELECT * FROM emp", , ADODB.CommandTypeEnum.adCmdText)
Me.DataGrid1.DataSource = rs


アップグレードウィザードを使った場合、データベースへのアクセスは、ADO.NET に変換されるわけではなく、ADO のラッパークラスを使って ADO のまま動作するように変換されます。また、DataGrid コントロールも Visual Basic 6.0 のコントロールがそのまま使われています。確かに、この実装でも正しく動作します。しかも開発者は殆どコードを変更することなく、Visual Basic .NET に移行することができます。しかしながら、この実装方法には、いくつか注意しなければならない点があります。ラッパークラスを返して ADO を使うことにより、オーバーヘッドが発生し、従来の Visual Basic 6.0 で作成したアプリケーションよりパフォーマンスが低下します。本来、.NET Framework が提供する ADO.NET を使用することによる、パフォーマンスの向上や、非接続型のデータアクセスと言った .NET のメリットを十分に発揮できません。上記のコードを、アップグレードウィザードを使用せずにODP.NET で実装したコードは以下のようになります。

リスト.32 ADOからODP.NETへ手動でコードを変更したサンプル

Dim conn As New OracleConnection( _
   "User Id=Scott;Password=Tiger;Data Source=orcl")
Dim cmd As New OracleCommand("Select * from emp", conn)
Dim adp As New OracleDataAdapter(cmd)
Dim ds As New DataSet
adp.Fill(ds, "emplist")
DataGrid1.SetDataBinding(ds, "emplist")

コードの変更以外にも、フォームで使用しているコントロールの移行も必要になります。上記のアプリケーションは、Visual Basic 6.0で使用しているDataGridコントロールをVisual Studio .NETのDataGridコントロールに変更しています。同じDataGridコントロールでも仕様は異なりますのでご注意ください。