次の方法で共有


Java プログラマー向けの ADO.NET

 

Microsoft Corporation
2003 年 10 月

適用対象
    Microsoft® ASP.NET
    Microsoft ADO.NET

概要: ADO.NET を使用して、JDBC などの Java ベースのデータ アクセス テクノロジからデータベース アクセス操作に移行する方法について説明します。 (22ページ印刷)

内容

はじめに
JDBC
JDBC の JLCA 変換
サンプル コードの変換
www.codenotes.com の変換
ADO.NET
まとめ

はじめに

データ アクセス テクノロジは継続的に進化しています。 最初に、データベース ベンダーは、アプリケーションをデータベース管理システムにネイティブにバインドする独自の C ライブラリをリリースしました。 それ以来、Microsoft® では、ODBC、DAO、OLE DB、RDO、ADO など、さまざまなデータ アクセス ツールをリリースしてきました。 ODBC は、汎用データベース プログラミングの柔軟性を活用するために広く受け入れられた最初のテクノロジでした。 OLE DB と ADO は、COM プログラマの世界にデータ アクセス ツールを提供するために構築されました。 このテクノロジ ファミリの最新の進化である Microsoft ADO.NET は、リレーショナル データベース へのアクセスを Microsoft .NET 環境に統合します。

Java 側では、JDBC は ODBC の成功後にモデル化されました。 アイデアは、開発者向けの汎用データ アクセス インターフェイスを定義することでした。その後、独立したベンダーは、これらのインターフェイスの競合する実装を提供します。 その後、開発者は、アプリケーションに最適な実装を自由にプラグインできます。

この記事では、まず Java でのデータベース アクセスについて簡単に説明します。 その後、変換プロセス中に Java Language Conversion Assistant (JLCA) がデータベースアクセス コードに加える変更と、このコードに対して行う必要がある変更について説明します。 最後に、ADO.NET を使用してデータベースを操作するさまざまな方法の概要を示します。

JDBC

JDBC は、さまざまな Java クラスを介してリレーショナル データベース アクセスをカプセル化します。 すべての JDBC 操作には、明示的に開いて閉じる必要がある JDBC 接続オブジェクトが必要です。 接続では、通常、JDBC ステートメント オブジェクトを使用して、実行する SQL 操作を指定します。 INSERT、UPDATE、DELETE などの通常の SQL ステートメントとストアド プロシージャの両方を、Statement インターフェイスの適切なサブクラスを使用して実行できます。 通常、ステートメントの実行結果は JDBC レコード セット オブジェクトに格納されます。これにより、リレーショナル データベースで表される形式と同様に、クエリの結果が行単位の形式で公開されます。 リスト 1.1 は、Statement オブジェクトを使用して単純な SQL クエリを実行する簡単な例と、 CallableStatement を使用してデータベースを更新するストアド プロシージャを呼び出す例を示しています。

リスト 1.1。 JDBC を使用したデータベースの操作

import java.sql.*;
public class SimpleQuery {
/* it is only necessary to have the Class.forName() call 
to be executed once during the running of the application, 
but it is included in each method in this example to 
demonstrate that Class.forName() must be called before you can 
create a connection
*/
// sql is any SQL statement, for example: 
// SELECT * FROM books
public void executeQuery(String sql) {
   try {
      Class.forName("com.microsoft.jdbc.sqlserver.SqlServerDriver");

      Connection conn =
         DriverManager.getConnection("jdbc:microsoft:sqlserver:" +
         "//databaseName=codenotes", "sa", "");
      Statement stmt = conn.createStatement();
      ResultSet rs = stmt.executeQuery(sql);

      long numCols = rs.getMetaData().getColumnCount();
      while (rs.next()) {
         for (int i=1; i<numCols; i++) {
            if (i>1)
               System.out.print(", ");

               System.out.print(rs.getString(i));
         } // for  System.out.println();
      } // while

      rs.close();
      stmt.close();
      conn.close();

      } catch (Exception e) {
         e.printStackTrace();
      } // try
   } // executeQuery


   public void execStoredProcExample() {
      try {
         Class.forName("com.microsoft.jdbc.sqlserver.SqlServerDriver");

         Connection conn =
            DriverManager.getConnection("jdbc:microsoft:sqlserver:" +
            "//databaseName=codenotes","sa", "");
         CallableStatement cStmt = 
            conn.prepareCall("{call sp_addFeaturedArticle(?)}");
         cStmt.setString(1, "AS010010");
         cStmt.execute();

         cStmt.close();
         conn.close();
         } catch (Exception e) {
            e.printStackTrace();
      } // try
   }
} // SimpleQuery

ストアド プロシージャを呼び出すときに、疑問符プレースホルダーを使用してパラメーターを使用していることに注意してください。

CodeNotes サイトでは、すべてのデータベース アクセスが JDBC を使用して実行されます。 CodeNotes データベースとの対話は、データベースへのアクセスを必要とするサードパーティ製のソフトウェア コンポーネント (Jive Forums など) を除き、ストアド プロシージャを通じて完全に行われます。 すべてのストアド プロシージャの実行は、 com.codenotes.db.util.DBUtil クラスに含まれるさまざまなメソッドによって制御されます。 DBUtil クラスは、Connection、実行するストアド プロシージャの名前を表す String、およびストアド プロシージャのパラメーターを含む Vector を受け入れるメソッドを公開します。 DBUtil クラスには、指定されたストアド プロシージャ名とパラメーターに対応する CallableStatement を作成するさまざまなプライベート メソッドもあります。 DBUtil クラスのメソッドは、さまざまな DBManager クラス (com.codenotes.db.dbmanager パッケージにあります) によって呼び出されます。このクラスは、ユーザーが入力した情報を、データベースとの対話に必要なパラメーターとストアド プロシージャに解釈します。

JDBC の JLCA 変換

JLCA は、OLE DB データ プロバイダーを使用して JDBC コードを C# コードに変換します。 OLE DB データ プロバイダーは、単一のリレーショナル データベース システムに固有のものではありません。データベース用の OLE DB ドライバーがある場合は、プロジェクトで機能します。 ただし、データ プロバイダーに関するトピックで後述するように、OLE DB データ プロバイダーを使用すると、 System.Data.OracleClient 名前空間に含まれる Oracle データ プロバイダーなどのデータベース固有のプロバイダーを使用するよりも、データベースを操作する方法が遅くなります。 ほとんどのプロジェクトでは、データベース システムを柔軟に変更する必要がなく、データベース固有のデータ プロバイダーの選択に関連する機能が失われるわけではないため、できるだけ早くデータベース データ プロバイダーを組み込む必要があります。 また、「データ プロバイダー」トピックでも説明しますが、すべてのデータ プロバイダーは同じ基本的なインターフェイスを実装するため、変換されたコードをクリーンアップしてデータベース固有のデータ プロバイダーを操作することは、通常、OLE DB データ プロバイダーと連携するコードをクリーンアップするよりも多くの余分な作業ではありません。 このため、コードを OLE DB に一時的に変換するのではなく、DBMS のデータ プロバイダーを使用して にコードを直接変換する必要があります。

OLE DB データ プロバイダーとデータベース固有のデータ プロバイダーのどちらを使用するかに関係なく、コードで使用する接続文字列を変更する必要があります。 接続文字列の形式は、使用しているデータ プロバイダーに固有のものですが、Microsoft SQL Server ™ データベースで OLE DB データ プロバイダーを使用している場合、接続文字列は次のようになります。

リスト 1.2。 SQL Server用の OLE DB 接続文字列

Provider=SQLOLEDB;Integrated
Security=SSPI;Data Source=localhost;Initial Catalog=pubs

接続文字列に加えて、SQL ステートメント自体に変更を加える必要がある場合もあります。 INSERT ステートメントや UPDATE ステートメントなどの単純な SQL ステートメントを使用している場合、JLCA によって生成されるコードの大部分は、ユーザーによる変更なしで機能します。 ただし、ユーザー入力を含む文字列連結を使用して SQL ステートメントを作成する場合は、ADO.NET Command オブジェクトのパラメーター機能を利用して、悪意のあるユーザー入力に関連する脅威から身を守る必要があります。 ただし、単純な SQL ステートメントの代わりにストアド プロシージャを広範囲に使用する場合、JLCA によって生成されるコードは機能せず、すべてのストアド プロシージャ呼び出しを ADO.NET で受け入れられる形式に手動で変換する必要があります。

サンプル コードの変換

リスト 1.1 の変換では、各メソッドに次の 3 つの変更を加える必要があります。

  1. JDBC ドライバー マネージャーの明示的な読み込みを削除しました。 ADO.NET では、ドライバー マネージャーが自動的に読み込まれるので、ドライバー マネージャーをインスタンス化するために Class.forName() メソッドと同等の C# を呼び出す必要はありません。 このエラーは、 Class.forName() から System.Type.GetType() への変換によって発生する可能性のある問題として、JLCA によってフラグが設定 されます

  2. 接続文字列を ADO.NET 形式に変換する必要があります。 接続文字列の目的の形式は、使用するデータ プロバイダーによって異なりますが、OLE DB データ プロバイダーの場合、接続文字列の形式は次のとおりです。

    "Provider=SQLOLEDB;Database=codenotes;User ID=sa;Password="
    

  3. Statement.close() の呼び出しを削除する必要があります。 ADO.NET では、Statement オブジェクトに相当する Command オブジェクトを閉じる必要はありません。 このため、 Statement.close() メソッドに相当する C# はありません。そのため、変換されたコードでは元のメソッド呼び出しが JLCA によって終了され、エラーにアップグレードの問題としてフラグが設定されます。

各メソッドに対して行う必要があった 3 つの変更に加えて、ストアド プロシージャの呼び出し方法も変更する必要がありました。 これには、 execStoredProcExample() の行の一部を書き換える必要があります。 ADO.NET では、ストアド プロシージャを呼び出すときに、Command オブジェクトの CommandText プロパティがストアド プロシージャの名前に設定され、すべてのパラメーターが名前に従って Command オブジェクトの Parameters プロパティに追加されます。 これは、SQL 文字列内の関連する疑問符のインデックスに従ってパラメーターが設定された Java 構文とは完全に異なるため、JLCA ではこのコードをクリーンに変換できませんでした。 Command オブジェクトの詳細と、Command オブジェクトと Statement オブジェクトの違いについては、「ADO.NET」セクションを参照してください。

変換後の変更を含むリスト 1.1 の変換されたコードは、リスト 1.3 に示されています。 太字の行は、コードが JLCA 変換に追加された場所を示します。

リスト 1.3。 リスト 1.1 から JLCA で変換されたコードをクリーンアップした後

using System;
public class SimpleQuery {

/* it is only necessary to have the Class.forName() call to be 
executed once during the running of the application, but it is 
included in each method in this example to demonstrate that 
Class.forName() must be called before you can create a connection
*/

// sql is any SQL statement, for example:
// SELECT * FROM books
public virtual void executeQuery(System.String sql) {
   try {
      System.Data.OleDb.OleDbConnection temp_Connection;
      temp_Connection = new
         System.Data.OleDb.OleDbConnection("Provider=SQLOLEDB;" +
         "Database=codenotes;User ID=sa;Password=");
      temp_Connection.Open();
      System.Data.OleDb.OleDbConnection conn = temp_Connection;
      System.Data.OleDb.OleDbCommand stmt = 
      SupportClass.TransactionManager.manager.CreateStatement(conn);

      System.Data.OleDb.OleDbCommand temp_OleDbCommand;
      temp_OleDbCommand = stmt;
      temp_OleDbCommand.CommandText = sql;
      System.Data.OleDb.OleDbDataReader rs = 
         temp_OleDbCommand.ExecuteReader();
      long numCols = rs.GetSchemaTable().Rows.Count;

      while (rs.Read()) {
         for (int i = 1; i < numCols; i++) {
            if (i > 1)
               System.Console.Out.Write(", ");
               System.Console.Out.Write(System.Convert.ToString(rs[i – 
               1]));

         } // for
         System.Console.Out.WriteLine();
      } // while
      rs.Close();
      conn.Close();
   } catch (System.Exception e) {
      SupportClass.WriteStackTrace(e, Console.Error);
   } // try
} // executeQuery

public virtual void execStoredProcExample() {
   try {
      System.Data.OleDb.OleDbConnection temp_Connection;
      temp_Connection = new
         System.Data.OleDb.OleDbConnection("Provider=SQLOLEDB;" + "
         Database=codenotes;User ID=sa;Password=");

      temp_Connection.Open();
      System.Data.OleDb.OleDbConnection conn = temp_Connection;
      System.Data.OleDb.OleDbCommand cStmt = conn.CreateCommand();
      oStmt.CommandType = System.Data.CommandType.StoredProcedure;

      cStmt.CommandText = "sp_addFeaturedArticle";
      cStmt.Parameters.Add("@articleID", "AS010010"); 
      cStmt.ExecuteNonQuery();

      conn.Close();
   } catch (System.Exception e) {
      SupportClass.WriteStackTrace(e, Console.Error);
   } // try
}

} // SimpleQuery 

www.codenotes.com の変換

CodeNotes サイトでの最初の変換後の作業の大部分は、データベース操作コードの変換の修正に関連しています。 主に次の 3 つのタスクが関係していました。

  • OLE DB データ プロバイダーの使用を、System.Data.SqlClient 名前空間に含まれていたSQL Server データ プロバイダーに変更しました。
  • CachedRowSet オブジェクトを使用する代わりに、DataSet オブジェクトを使用するようにコードを更新する必要がありました。 CachedRowSet は JDBC ドライバーによって提供され、 RowSet インターフェイスの拡張機能です。これにより、データベースへの接続が閉じられた後に RowSet を調べることができます。 サードパーティのクラスであるため、JLCA は CachedRowSet を使用してコードを変換しませんでした。
  • JDBC 構文ではなく、ADO.NET 構文を使用するように DBUtil のメソッドを変更しました。

これらの各手順は、フェーズ 1 変換の一環として実際に実行されました。 OLE DB データ プロバイダーに必要な処理を行わせようとするよりも、SQL Server データ プロバイダーに直接変換する方が迅速かつ簡単だと判断しました。 これは、多くの場合、アプリケーションで当てはまる場合があります。

ADO.NET

JDBC と ADO.NET は機能的には似ていますが、基になる前提条件とアーキテクチャは異なります。 JDBC は、Java プログラミング言語の主な目標であるプラットフォームの独立性と仕様内のイノベーションを維持しながら、汎用のデータ アクセス用に設計されました。 一方、ADO.NET 一般的な汎用データ アクセスに加えて、分散環境でのデータ アクセスにも対応するように設計されています。 特に、ADO.NET は、接続されたモデルと切断されたモデルという 2 つの異なるデータ モデルを実装します。 接続されたデータ モデルは JDBC とほぼ同じように機能し、すべてのデータ操作は開いているデータベース接続のスコープ内に含まれています。 切断されたデータ モデルは、絶対に必要な場合にのみデータベース接続を開きます。データベースのクエリまたは変更中のみである。 操作が実行されると、操作の開始時と終了時に、データベース接続が暗黙的に開かれて閉じられます。 このデータ モデルにより、複数のユーザー間で接続を共有する機能が大幅に強化されます (Web ベースの環境では明らかな利点があります)。 切断されたモデルに相当する Java は、JDBC ドライバーの一部として使用できる場合と使用できない場合がありますが、すべての ADO.NET データ プロバイダーでは、切断されたモデルが存在する必要があります。 ADO.NET は、XML と緊密に統合されています。

データ プロバイダー

ADO.NET データ プロバイダーは、Java 環境で JDBC ドライバーが果たすので、.NET 環境で同義の役割を果たしています。 データ プロバイダーは、JDBC ドライバーと同様に、基になるデータ ソースへのアクセスを提供するインターフェイスとクラスのセットです。 すべてのデータ プロバイダーは、すべての開発者が実装に依存できるインターフェイスの最小セットを公開します。ただし、プロバイダーは必要最小限の機能を自由に拡張できます。 たとえば、SQL Server データ プロバイダーを使用すると、SQL Serverデータ型のクラスを含む System.Data.SqlTypes 名前空間を操作できるため、データ型から.NET Frameworkデータ型への型変換エラーをSQL Serverできなくなります。

すべてのデータ プロバイダーは、少なくとも次の 4 つのインターフェイスを実装する必要があります。

  • System.Data.IDbConnection は、実際のデータベースとの接続を確立します。 このインターフェイスは、 java.sql.Connection インターフェイスと同様の機能を提供します。
  • System.Data.IDbCommand は、データベース接続を介してコマンドを実行します。 このインターフェイスは、 java.sql.Statement インターフェイスとその子孫インターフェイス java.sql.PreparedStatement および java.sql.CallableStatement と同様の機能を提供します。
  • System.Data.IDataReader は 、SQL クエリの結果を反復処理します。 開いているデータ接続が必要です。 このインターフェイスの機能は、 java.sql.ResultSet インターフェイスに似ています。
  • System.Data.IDataAdapter は、切断されたシナリオで使用され、データベースとアプリケーションの間の仲介役として機能します。 開いているデータ接続は必要ありません。 このインターフェイスと同等の JDBC はありません。

.NET Frameworkには 5 つの異なるデータ プロバイダーが付属しており、それぞれが特定の名前空間に含まれています。 データ プロバイダーの 3 つがデータベース固有です。

  • Oracle データ プロバイダー ( System.Data.OracleClient 名前空間内)
  • SQL Server データ プロバイダー(SQL Server 7.0 以降)(コアは System.Data.SqlClient 名前空間に含まれていますが、前述のように、追加のクラスも System.Data.SqlTypes 名前空間に含まれています)。
  • SQL Server CE データ プロバイダー (System.Data.SqlServerCE 名前空間内)

他の 2 つのデータ プロバイダーはデータベース固有ではなく、2 つの Microsoft の古いデータ アクセス ツールを使用してデータ ソースと対話します。

  • ODBC データ プロバイダー ( System.Data.Odbc 名前空間内)
  • OLE DB データ プロバイダー ( System.Data.OleDb 名前空間内)

データベース用の ODBC ドライバーまたは OLE DB ドライバーがある場合は、対応するデータベース 汎用データ プロバイダーを使用して、ADO.NET 操作を実行できます。 ただし、これら 2 つのプロバイダーはデータベース固有ではないので、メモリとパフォーマンスのオーバーヘッドを追加するネイティブ データ プロバイダーよりも、実行するソフトウェアの層が多くなります。 また、汎用メソッド呼び出しへの抽象化により、データベース固有の機能が失われるので、データベース管理システムを完全に活用することはできません。

コマンド

ADO.NET コマンド オブジェクトは、JDBC ステートメント オブジェクトと同様の機能を提供します。 どちらも、SQL の送信と、基になる DBMS からの結果の取得を担当します。 ただし、動的 SQL ステートメントの Statement クラス、静的で再利用可能な SQL ステートメントの PreparedStatement クラス、ストアド プロシージャを呼び出すための CallableStatement クラスを持つ JDBC とは異なり、すべての SQL ステートメントとストアド プロシージャ呼び出しは、コマンド クラスのインスタンスを使用して実行されます。

コマンド オブジェクトには、データベースとの対話を指定するために使用される 3 つのプロパティがあります。

  • CommandText - ストアド プロシージャを呼び出すときに、呼び出すストアド プロシージャの名前が含まれます。 SQL テキストを実行するときに、実行する SQL ステートメントが含まれます。
  • CommandType — ストアド プロシージャや SQL テキストなど、実行されるステートメントの種類を指定します。
  • Parameters — ストアド プロシージャまたは SQL テキストを呼び出すときに使用するパラメーターを指定します。 ADO.NET を使用すると、文字列連結を使用する代わりに SQL テキスト コマンドでパラメーターを使用して、文字列連結によってもたらされるセキュリティ上の脅威を回避できます。

次のセクションでは、コマンド オブジェクトを使用してデータベースを操作する方法について説明します。リスト 1.4 では、SQL テキスト、ユーザー入力を使用した SQL テキスト、またはストアド プロシージャを呼び出すコマンド オブジェクトを作成する方法を示します。

リスト 1.4。 プレーン SQL テキスト、ユーザー入力を含む SQL テキスト、ストアド プロシージャ呼び出し用のコマンド オブジェクトの作成

// conn is an instance of the System.Data.IDbConnection interface 
// creating a plain text command object 
IDbCommand cmdPlainText = conn.CreateCommand(); 
cmdPlainText.CommandType = System.Data.CommandType.Text;
cmdPlainText.CommandText = "SELECT * FROM titles"; 

// creating a plain text command object using user input 
// the resulting statement will be 
// "SELECT * FROM titles WHERE title_id=myTitleID" 
IDbCommand cmdUserInputText = conn.CreateCommand(); 
cmdUserInputText.CommandType= System.Data.CommandType.Text;
cmdUserInputText.CommandText = "SELECT * FROM titles WHERE 
   title_id=@title_id"; 

/* note the previous line could have been: 
cmdUserInputText.CommandText = 
"SELECT * FROM titles WHERE title_id='" + "myTitleID" + "'"
and have the same effect on the database, 
but that would expose a security risk */ 
IDbDataParameter userParam = cmdUserInputText.CreateParameter();
userParam.ParameterName = "@title_id"; 
userParam.Value = "myTitleID";
userParam.DbType = System.Data.DbType.String; 
cmdUserInputText.Parameters.Add(userParam); 

/* creating a command to call the stored procedure 
"spMyStoredProcsName" which has a single parameter "@spParam". */
IDbCommand cmdStoredProc = comm.CreateCommand(); 
cmdStoredProc.CommandType = System.Data.CommandType.StoredProcedure;
cmdStoredProc.CommandText = "spMyStoredProcsName";
IDbDataParameter spParam = cmdStoredProc.CreateParameter();
spParam.ParameterName = "@spParam";
spParam.Value = "spParamValue";
spParam.DbType = System.Data.DbType.String;
cmdStoredProc.Parameters.Add(spParam);

この例で使用したジェネリック パラメーター型 (System.Data.DbType.String など) に加えて、一部のデータ プロバイダーでは、SQL Server データベース内の VarChar データを表すために使用される System.Data.SqlDbType.VarChar など、データベースに固有のデータ型にマップされるパラメーター型も提供されます。 データベース固有のデータ型を使用するには、汎用 IDbDataParameter の代わりにデータ プロバイダーのパラメーター クラスを使用する必要があります。 データベース固有のデータ型を使用すると、データベースデータ型から.NET Frameworkデータ型への変換でデータが失われるのを回避できます。ただし、将来、別のデータベースを操作するように.NET Framework アプリケーションを変更することはより困難になります。 使用するデータ型の選択は、データベース内のデータの必要な精度 (ほとんどのデータ型はデータ損失なしで変換されます) と、将来データベースを変更する予定があるかどうかに基づいて決定されます。

cmdUserInputText コマンドを作成するときに、単に値 "myTitleID" を文字列に連結するのではなく、パラメーター オブジェクトを使用していることがわかります。 これは、ユーザーの入力を使用した文字列連結がセキュリティ上の脅威であるためです。 たとえば、次のコード行を使用する場合は、次のようになります。

// userInput is a String the user entered
myCommand.CommandText = "SELECT * FROM titles WHERE title_id=' +
   userInput + "'";

ユーザーは、"INSERT into" の値を入力することで、INPUT または他の SQL ステートメントを実行する可能性があります。文字列連結の代わりにパラメーターを使用すると、この悪意のあるアクションの可能性がなくなります。 JDBC と同等のパラメーターは、疑問符プレースホルダーを使用することです。

データベースに対するクエリの実行

ADO.NET のデータベースのクエリに使用される最も単純なパターンは、JDBC で使用されるパターンに似ています。 接続オブジェクトとコマンド オブジェクト (ステートメント オブジェクトと同等の ADO.NET) を作成します。 クエリの実行中、およびクエリの各行が 1 つずつ取得される間、データベースへの接続が維持されます。 メソッドがすべての行の処理を完了すると、接続は閉じられます。 次の簡単な例では、このパターンを使用してクエリの結果を表示します。

リスト 1.5。 接続されたモデルを使用して ADO.NET データベースに対してクエリを実行する

using System.Data; 
using System.Data.SqlClient; 
public class SimpleQuery {
// sqlCommand is a SELECT sql statement, such as 
// "SELECT * FROM titles" 
public void ExecuteQuery(String sqlCommand) {
   try {
      IDbConnection conn = 
         new SqlConnection("Initial Catalog=myDB;"
         "Data Source=localhost;Integrated Security=SSPI");
      IDbCommand cmd = conn.CreateCommand(); 
      cmd.CommandText = sqlCommand; 
      conn.Open(); 
      IDataReader reader = cmd.ExecuteReader(); 

      while (reader.Read()) {
         for (int i=0; i<reader.FieldCount; i++) {
            if (i>0) 
               Console.Out.Write(", ");
            Console.Out.Write(reader[i]);
         } // for 
         Console.Out.WriteLine();
      } // while 
   } catch (Exception e) { 
      Console.Out.WriteLine(e.Message);
   }
}
}

3 つのメイン オブジェクト (conncmdreader) の宣言では、クラス型は "I" (IDbConnectionIDbCommandおよび IDataReader) で始まる点に注意してください。 「データ プロバイダー」セクションで説明したように、これらのクラスは各データ プロバイダーによって実装されるインターフェイスです。 SQL Server データベースに固有の機能を使用していないため、これらのインターフェイスを使用すると、機能を失うことなく、将来の変更を別のデータ プロバイダーに簡単に導入できます。 たとえば、OLE DB データ プロバイダーを使用する場合、コードで変更する必要があるのは次の 2 つだけです。

  1. System.Data.SqlClient 名前空間を使用する代わりに、System.Data.OleDb 名前空間を使用します。
  2. conn オブジェクトの作成を次のように変更します。
    IDbConnection conn = new
    OleDbConnection("oledbconnectionstring");
    

切断されたデータ モデルを使用したクエリ

前述のように、ADO.NET を使用すると、操作の全期間にわたって開いている接続を維持することなく、データベースと対話することもできます。 切断されたデータ モデルのパターンは、接続されたモデルのパターンに似ていますが、異なるオブジェクトを使用し、一部の操作をこれらのオブジェクトに委任する点が異なります。 簡単な例をリスト 1.6 に示します。

リスト 1.6。 切断されたモデルを使用したデータベースのクエリ

private void DisconnectedQuery(String sql) {
   try  { 
      IDbConnection conn = 
         new SqlConnection("Integrated Security=" +
         "SSPI;Data Source=localhost;Initial Catalog=pubs");
      SqlDataAdapter da = new SqlDataAdapter(sql,(SqlConnection)conn);
      DataSet ds = new DataSet();
      da.Fill(ds); 
      foreach (DataRow r in ds.Tables[0].Rows) {
         for (int i=0; i<r.ItemArray.Length; i++) {
            if (i>0) 
               Console.Out.Write(", ");
            Console.Out.Write(r.ItemArray[i]);
         } // for
         Console.Out.WriteLine();
      } // foreach 
      textBox1.Text = sb.ToString();
   } catch (Exception x) { 
      Console.Out.WriteLine(x.Message);
   } // try
}

Fill() メソッドの最後に、DataSet オブジェクトには、そのテーブル内のクエリの結果のメモリ内スナップショットが含まれます。 リスト 1.6 のように、1 つの SELECT ステートメントを実行すると、クエリの結果は Tables コレクションの最初のテーブルに格納されるため、ds を使用して結果にアクセスします。Tables[0]. 次のセクションで説明するように、メモリ内スナップショットデータを変更し、変更をデータベースに送信することができます。 このようにして、データベース接続は可能な限り最小限の時間維持され、接続オブジェクトの再利用性が大幅に向上します。 ただし、切断されたデータ モデルは、メモリの制約がある状況には適していない可能性があることに注意してください。

リスト 1.6 では、すべてのデータベース アクティビティが da 内で発生します 。Fill() メソッド。 Fill() メソッドには、次の責任があります。

  1. データベース接続を開きます (コンストラクターで渡されます)。
  2. SQL コマンド (コンストラクターでも渡されます) をデータベースに送信します。
  3. クエリの結果のすべての行をアプリケーションに返します。 特に、 DataSet オブジェクト内のテーブル内のすべての行を保持します。
  4. データベース接続を閉じます。

または、SQL テキストに文字列を使用する代わりに、データ アダプターは、前述の「コマンド」セクションで説明したコマンド オブジェクトを操作できるため、ストアド プロシージャを呼び出したり、SQL テキストの実行時にパラメーターを使用したりできます。 ただし、コマンド オブジェクトを使用している場合でも、 Fill() メソッドは引き続き同じ 4 つの職務を担当します。

リスト 1.7。 コマンド オブジェクトを使用する DataAdapter

// myCmd is a command objected created in any of the
// manners shown in Listing 1.4
DataSet ds = new DataSet();
SqlDataAdatper da = new SqlDataAdapter(myCmd);
da.Fill(ds);

データベースの状態の変更

データベースのクエリに対する裏返しは、行の挿入、更新、または削除です。 ADO.NET は、JDBC と同様の方法でデータベースを更新します。 ADO.NET でのデータベースの変更は、ExecuteQuery() メソッドの代わりに ExecuteNonQuery() メソッドが呼び出される点を除き、ADO.NET でデータベースに対してクエリを実行する場合とほぼ同じです。 また、クエリ以外のステートメントを実行しているため、DataReader オブジェクトではなく、データベースに更新または追加された行数を反映する intExecuteNonQuery() から返されます。 データベースを更新するための簡単な方法をリスト 1.8 に示します。 ステートメントを更新するために呼び出される SQL コマンドと メソッドとは別に、ADO.NET コードはリスト 1.5 のコードと同じであることに注意してください。

リスト 1.8。 ADO.NET を使用したデータベースの更新

using System.Data; 
using System.Data.SqlClient;
public class SimpleNonQuery {
   // sql is any string that would cause the db to be
   // updated, such as 
   // "UPDATE Products SET UnitPrice = UnitPrice * 1.1"
   public void ExecuteUpdateDB(String sql) {
      try {
         IDbConnection conn = 
            new SqlConnection("Integrated Security=SSPI;" +
            "Data Source=localhost;Initial Catalog=pubs");
         conn.Open(); 
         IDbCommand cmd = conn.CreateCommand();
         cmd.CommandText = sql;
         cmd.CommandType = System.Data.CommandType.Text;
         int result = cmd.ExecuteNonQuery();
         conn.Close();
      } catch (Exception e) {
         Console.Out.WriteLine(e.Message);
      } // try
   } // ExecuteNonQuery
} // SimpleNonQuery

切断されたデータ モデルを使用した更新

切断されたデータ モデルを使用してデータベースを更新することは、切断されたモデルを使用してデータベースに対してクエリを実行するプロセスの逆と考えることができます。 SQL ステートメントを実行してから DataSet を作成する代わりに、 DataSet が調べられ、SQL ステートメントが作成され、実行されます。 パラメーターとして DataSet を受け取る IDataAdapter.Update() メソッドは、データベースの更新を実行します。 Update() メソッドの実行中、DataAdapter は次のアクションを実行します。

  1. データベースへの接続を開きます。
  2. DataSet の各行を行ごとに調べます。
  3. データベースとの前回の対話以降に行が変更された場合は、それに応じて INSERT、UPDATE、または DELETE SQL ステートメントが実行されます。 行の状態 (変更されていない、挿入、変更、または削除) は、DataTable の文書パーツである DataRow オブジェクトのプロパティとして保持され、さらに DataSet の文書パーツとして保持されます。
  4. 接続を閉じます。

実行される実際の INSERT、UPDATE、および DELETE SQL コマンドは、 DataAdapter オブジェクトのプロパティとして保持されます。 Update() メソッドを呼び出す前に、INSERT、UPDATE、および DELETE 操作の SQL コマンドを明示的に設定する必要があります。 それ以外の場合は例外がスローされ、適切なコマンドが存在しない場合、データベースは変更されません。 たとえば、 DataSet から行が削除され、 DeleteCommand プロパティが設定されていない場合、 Update() メソッドを呼び出すと例外がスローされ、削除された行はデータベースに残ります。 .NET フレームワークに含まれる各データ プロバイダーには、DataSet 内の DataTable オブジェクトの内容がデータベース内の 1 つのテーブルを表している場合に備えて、必要な SQL ステートメントを作成する CommandBuilder オブジェクトが含まれます (つまり、DataTable は結合操作の結果ではありません)。 ただし、データ プロバイダーが CommandBuilder オブジェクトを持つことは必須ではありませんが、その有用性と、.NET Frameworkに含まれるすべてのデータ プロバイダーに CommandBuilder クラスが含まれているため、ほとんどのサード パーティのデータ プロバイダーにも同様のユーティリティがあります。 リスト 1.9 では、SQL Server データ プロバイダーと、先ほど説明したクラスを使用して、DataSet を変更してデータベース内の情報を変更します。

リスト 1.9。 切断されたモデルを使用したデータベースの更新

// uses the System.Data namespace and the
// System.Data.SqlClient namespace
IDbConnection conn = new SqlConnection("Integrated Security=SSPI;" +
   Data Source=localhost;Initial Catalog=pubs");
String sql = "SELECT * FROM titles";
SqlDataAdapter da = new SqlDataAdapter(sql, (SqlConnection)conn);
DataSet ds = new DataSet();
da.Fill(ds);
SqlCommandBuilder myBuilder = new SqlCommandBuilder(da);
ds.Tables[0].Rows[0]["title"] = "Twenty Years After";
da.Update(ds);

Update() メソッドの SQL ステートメントはバッチで実行されず、代わりに一度に 1 回ずつ実行されることに注意してください。 また、ほとんどのデータ プロバイダーは Update() メソッドに対して複数のオーバーライドを提供するため、さまざまなオブジェクトを使用してデータベースを更新できます。 たとえば、SQL Serverの多くの更新オプションの 1 つは、DataSet オブジェクトではなく DataRow オブジェクトの配列を使用することです。

接続のプール

接続をプールすると、アプリケーションのパフォーマンスとスケーラビリティを大幅に改善できます。 ADO.NET では、接続プールはデータ プロバイダーの責任であり、従う必要がある標準セットはありません。 このセクションでは、SQL Server データ プロバイダーを使用した接続プールについて説明しますが、このプロセスは、.NET Frameworkに含まれる他のデータ プロバイダーに似ています。 使用するデータ プロバイダーの詳細については、MSDN® を参照してください。

データベースへの実際の接続は、接続を呼び出すときにのみSQL Serverデータ プロバイダーによって作成されます。Open() メソッド。 複数の接続オブジェクトを作成しても、Open() メソッドを呼び出さない場合、SQL Serverデータ プロバイダーは実際にはデータベースへの接続を作成しません。 Open() メソッドが呼び出されると、SQL Server データ プロバイダーには 2 つのメイン責任があります。

最初に、指定された接続文字列に対して接続プールが存在するかどうかを確認します。 各接続プールが別の接続文字列に関連付けられます。 指定された接続文字列に対して接続プールが作成されていない場合は、新しい接続プールが作成されます。 接続プールが作成されると、メイン プログラム プロセスが終了するまでは破棄されません。 つまり、接続プールを作成したら、プログラムの有効期間中は同じプールを保持します。

次に、データ プロバイダーは、この接続プールの接続オブジェクトの最小数が作成されているかどうかを確認します。 最小数が作成されていない場合、データ プロバイダーは接続オブジェクトを作成して返します。 接続オブジェクトの最小数が作成され、空き接続オブジェクトがある場合は、空き接続オブジェクトの 1 つが返されます ( Connection.Close() メソッドまたは Connection.Dispose() メソッドを呼び出して、接続オブジェクトが解放され、プールに対して解放されます)。 接続オブジェクトの最小数が作成され、空き接続がない場合、データ プロバイダーは接続オブジェクトの最大数が作成されているかどうかを確認します。 最大値に達していない場合は、新しい接続オブジェクトが作成されます。それ以外の場合、接続の要求は、既存の接続が空になるまでキューに配置されます。

接続オブジェクトを開くと、 Close() メソッドまたは Dispose() メソッドが呼び出されるまで、この接続はプールで再度使用できなくなります。 このため、必要になるまで接続を かないようにし、できるだけ早く Close() または Dispose() を呼び出す必要があります。 Close() を呼び出さずにスコープ外に移動する接続オブジェクト (たとえば、メソッド内で接続オブジェクトをインスタンス化して開くが、メソッド内で接続オブジェクトを閉じることはありません) は、すぐに接続プールに返されません。 スコープ外の接続は、最大プール サイズに達した場合にのみ接続プールに返されます。

接続文字列のパラメーターを使用して、接続プールを構成できます。 たとえば、リスト 1.10 に示すように、プール内の接続オブジェクトの最小数と最大数をそれぞれ設定するには、[最大プール サイズ] と [最小プール サイズ] を使用します。

リスト 1.10。 接続プールのサイズの構成

IDbConnection conn =
   new SqlConnection("Integrated Security=SSPI;" +
      "Data Source=localhost;Initial Catalog=pubs;"
      "Max Pool Size=15;Min Pool Size=5");

トランザクション

場合によっては、すべてまたは何もない方法で複数の SQL ステートメントを実行する必要があります。 たとえば、あるクライアントから別のクライアントへの銀行送金では、あるクライアントの口座に借方があり、もう一方のクライアントの口座への借方が必要です。 デビットを実行するだけ、またはクレジットを実行するだけでは受け入れられません。 このようなインスタンスでは、データベース トランザクションを使用します。

トランザクション処理には、標準データベースの変更と同じ基本的な手順と、いくつかの追加の手順が含まれます。 トランザクションの開始時に、Connection.BeginTransaction() メソッドを呼び出すことによって実行される IDbTranscation インターフェイスのインスタンスを作成する必要があります。 次に、 Command.Transaction を使用して、トランザクション オブジェクトをコマンド オブジェクトに割り当てる必要があります。 このコマンド オブジェクトは、トランザクションのすべてのステートメントを実行するために使用されます。 最後に、トランザクションが完了したら、 IDbTransaction.Commit() メソッドを呼び出してデータベースを更新します。 エラーが発生した場合、または他の理由でトランザクションを中止する場合は、 IDbTransaction.Rollback() メソッドを呼び出して、データベースをトランザクションの前の状態に戻します。 リスト 1.11 では、トランザクション処理を使用して、pubs データベースの sales テーブルに 3 つの異なるアイテムの発注書を挿入します。

リスト 1.11。 ADO.NET でのトランザクション処理

IDbConnection conn = new
   SqlConnection("Integrated Security=SSPI;" + 
      "Data Source=localhost;Initial Catalog=pubs");
conn.Open(); 
IDbTransaction trans = conn.BeginTransaction();
IDbCommand cmd = conn.CreateCommand();
cmd.Transaction = trans; 
try {
   cmd.CommandType = System.Data.CommandType.Text;
   cmd.CommandText = "INSERT INTO sales" +
      (stor_id, ord_num, ord_date, qty, payterms, title_id)" +
      " VALUES ('7896', 'P3456', '04/15/93', '10', 'Net 30',
        'BU1111')";
   cmd.ExecuteNonQuery();
   cmd.CommandText = "INSERT INTO sales" +
      (stor_id, ord_num, ord_date, qty, payterms, title_id)" +
      " VALUES ('7896', 'P3456', '04/15/93', '15', 'Net 30',
        'BU1032')";
   cmd.ExecuteNonQuery();
   cmd.CommandText = "INSERT INTO sales" +
      (stor_id, ord_num, ord_date, qty, payterms, title_id)" +
      " VALUES ('7896', 'P3456', '04/15/93', '10', 'Net 30',
        'PS2091')";
   cmd.ExecuteNonQuery();
   trans.Commit();
} catch (Exception x) {
   trans.Rollback();
} // try

トランザクションを作成する前に、データベースへの接続を開きます。 これは、トランザクション オブジェクトの作成にはデータベースとの対話が必要であり、それ以外の場合は機能しないためです。 また、Transaction プロパティの割り当て以外のコマンド オブジェクトは、 トランザクション なしでデータベースを変更する際のコマンド オブジェクトと同じ方法で使用されます。

ADO.NET での XML サポート

データベース ベンダーは、業界の将来における XML の重要性を認識しています。 XML のネイティブ サポートは、さまざまな独自のデータベース製品に組み込まれており、WEB サービスなどのプロトコルの出現に伴い、XML の使用は拡大し続けます。 そのことを念頭に置いて、Microsoft は XML を ADO.NET に統合しました。 たとえば、XML と DataSet を 1 つのメソッド呼び出しで交換できます。 つまり、XML ファイルを取得し、その内容をデータベース (逆に) にフラッシュして、データベース クエリを XML として表す単純で簡単なプロセスです。 また、 DataSet オブジェクトは、生データの格納に加えて、列制約とテーブル リレーションシップをモデル化して適用できます。 これらの制約は、XML スキーマを使用して XML に変換することもできます。 リスト 1.12 では、ADO.NET の XML 機能を使用して、データベースに対して単純なクエリを実行し、クエリの内容を XML ファイルに保存します。 XML ファイルのサンプルは、リスト 1.13 に示されています。

リスト 1.13。 リスト 1.12 によって生成された XML ファイルの内容

IDbConnection conn = 
   new SqlConnection("Integrated Security=SSPI;" +
      "Data Source=localhost;Initial Catalog=pubs");
String sql = "SELECT * FROM titles";
SqlDataAdapter da = new SqlDataAdapter(sql, (SqlConnection)conn);
DataSet ds = new DataSet();
da.Fill(ds);
ds.WriteXml("Titles.XML", XmlWriteMode.WriteSchema);
Listing 1.12 Saving the contents of a query to an XML file

<?xml version="1.0" standalone="yes"?>
<NewDataSet>
  <xs:schema id="NewDataSet"  xmlns:xs="
  http://www.w3.org/2001/XMLSchema"
    xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="NewDataSet" msdata:IsDataSet="true"
       msdata:Locale="en-CA">
      <xs:complexType>
        <xs:choice maxOccurs="unbounded">
          <xs:element name="Table">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="title_id"
                   type="xs:string" minOccurs="0" />
                <xs:element name="title"
                   type="xs:string" minOccurs="0" />
                <xs:element name="type"
                   type="xs:string" minOccurs="0" />
                <xs:element name="pub_id"
                   type="xs:string" minOccurs="0" />
                <xs:element name="price"
                   type="xs:decimal" minOccurs="0" />
                <xs:element name="advance"
                   type="xs:decimal" minOccurs="0" />
                <xs:element name="royalty"
                   type="xs:int" minOccurs="0" />
                <xs:element name="ytd_sales"
                   type="xs:int" minOccurs="0" />
                <xs:element name="notes"
                   type="xs:string" minOccurs="0" />
                <xs:element name="pubdate"
                   type="xs:dateTime" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
    </xs:element>
  </xs:schema>
  <Table>
    <title_id>BU1111</title_id>
    <title>Cooking with Computers: Surreptitious Balance Sheets</title>
    <type>business </type>
    <pub_id>1389</pub_id>
    <price>11.9500</price>
    <advance>5000.0000</advance>
    <royalty>10</royalty>
    <ytd_sales>3876</ytd_sales>
    <notes>Helpful hints on how to use your electronic resources to the
      best advantage.</notes>
    <pubdate>1991-06-09T00:00:00.0000000-04:00</pubdate>
</Table>
<Table>
  <title_id>BU2075</title_id>
  <title>You Can Combat Computer Stress!</title>
  <type>business </type>
  <pub_id>0736</pub_id>
  <price>2.9900</price>
  <advance>10125.0000</advance>
  <royalty>24</royalty>
  <ytd_sales>18722</ytd_sales>
  <notes>The latest medical and psychological techniques
    for living with the electronic office. Easy-to-understand
      explanations.</notes>
  <pubdate>1991-06-30T00:00:00.0000000-04:00</pubdate>
</Table>
<!-- more table entries-->
</NewDataSet>

リスト 1.12 の唯一の新しいメソッドは DataSet.WriteXml() メソッドです。このメソッドは、名前が示すように、クエリの内容を XML 形式で指定されたファイルに書き込みます。 XML 全体が NewDataSet> 要素内に<カプセル化されます。 titles テーブルの各行は、Table> 要素内に<カプセル化されます。 これは、SQL Server SELECT ステートメントの結果が名前のないテーブルであるためです。 da を呼び出す代わりに、リスト 1.14 に示されているコマンドを使用して、DataSet を使用してテーブルに名前を指定できました。Fill(ds) は、リスト 1.13 で行ったように、リスト 1.14 のコードを使用していた場合、XML ファイル内の各エントリは Table> 要素ではなく <NewTableName> 要素内<にカプセル化されていました。

リスト 1.14 データセット内のテーブルの名前付け

da.Fill(ds, "NewTableName");

また、リスト 1.13 でわかるように、XML スキーマは結果の XML ファイルに埋め込まれています。 XML スキーマの説明は、このホワイトペーパーの範囲外です。ただし、各列には名前と対応する型が指定されていることに注意してください。 この例では XML 内に XML スキーマを埋め込むのは、純粋に省略可能でした。 もう 1 つのオプションは、DataSet.WriteXml() を呼び出すときに XmlWriteMode.IgnoreSchema 列挙を使用して XML をファイルに書き込むだけで、DataSet.WriteXmlSchema() メソッドを使用して XML スキーマをファイルに書き込まないか、XML スキーマを独自のファイルに書き込む方法です。

XML ファイルを DataSet に読み込むことも非常に簡単です。 DataSet.ReadXml() メソッドを使用するだけです。 次に例を示します。

ds.ReadXml("Titles.XML", XmlReadMode.Auto);

この場合、Titles.XML ファイル内のデータは DataSet オブジェクトに格納されます。 指定する XmlReadMode は、XML スキーマが XML ファイルに埋め込まれているかどうかによって異なります。 受信 XML に埋め込み XML スキーマが含まれることがわかっている場合は、 XmlReadMode.Fragment を使用できます。 その他のオプションには、スキーマの無視、スキーマが存在しない場合の推論、指定したとおりに最も適切なアクションの自動実行などがあります。

まとめ

JDBC と ADO.NET は、データベースにアクセスするときに同様の機能を提供します。 2 つのテクノロジの間には同様の機能があり、その使用の背後にあるロジックは似ていますが、2 つのパッケージのクラスとインターフェイスの違いは十分に優れているため、JLCA はすべてのコードをクリーンに変換するわけではありません。 ただし、必要な変更は単純で簡単です。 変換エラーを修正する際に、変換時に JCLA で使用される汎用 OLE DB データ プロバイダーではなく、データベース固有のデータ プロバイダーを使用するようにコードを変換する可能性が高く、アプリケーションとデータベース間の相互作用が高速化されます。

© Microsoft Corporation. All rights reserved.