ASP.NET 2.0 および ADO.NET 2.0 での汎用データ アクセス コードの記述

 

シャーラム・コズラヴィ博士
情報ソリューション

2005 年 4 月

適用対象:

   Microsoft ADO.NET 2.0
   Microsoft ASP.NET 2.0
   Microsoft .NET Framework 2.0
   Microsoft Visual Web Developer 2005 Express Edition Beta
   C# プログラミング言語

の概要: さまざまな ASP.NET 2.0 と ADO.NET 2.0 のツールと手法を使用して、汎用データ アクセス コードを記述する方法について説明します。 (18ページ印刷)

関連するサンプル コードをダウンロードします:GenericDataAccessSample.exe

紹介

ほとんどの Web アプリケーションには、基になるデータ ストアにアクセスして、の選択、更新の削除、の挿入などの基本的なデータ操作 実行するためのデータ アクセス コードが含まれています。 この記事では、ページ開発者がさまざまな ASP.NET 2.0 と ADO.NET 2.0 のツールと手法を利用して、さまざまな種類のデータ ストアにアクセスするために使用できる汎用データ アクセス コードを記述する方法について説明します。 データは、Microsoft SQL Server、Oracle、XML ドキュメント、フラット ファイル、Web サービスなど、さまざまなソースから取得されるため、汎用データ アクセス コードの記述は、データ ドリブン Web アプリケーションで特に重要です。

この記事では、ここで説明するすべてのコードのテスト ベッドとして単純な Web アプリケーションを使用します。 アプリケーションは、2つの部分で構成されています:最初の部分は、システム管理者がメーリングリストのすべての購読者にニュースレターを送信することができます。 2 番目の部分では、ユーザーはメーリング リストをサブスクライブまたはサブスクライブ解除できます。 この記事の最初の部分では、Microsoft SQL Server にアクセスしてサブスクライバーの一覧を抽出するための単純なデータ アクセス コード (図 1 を参照) を実装することから始めます。 コードは、記事の過程で変更され、より汎用的になります。

図 1. GetSubscribers メソッドは、すべてのサブスクライバーの一覧を抽出します。

public IEnumerable GetSubscribers()
{
    SqlConnection con = new SqlConnection();
    con.ConnectionString = @"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf";

    SqlCommand com = new SqlCommand();
    com.Connection = con;
    com.CommandText = "Select * From Subscribers";
    com.CommandType = CommandType.Text;

    DataSet ds = new DataSet();
    SqlDataAdapter ad = new SqlDataAdapter();
    ad.SelectCommand = com;

    con.Open();
    ad.Fill(ds);
    con.Close();

    return ds.Tables[0].DefaultView;
}

図 1 のデータ アクセス コードには、SqlConnectionSqlCommand、SqlDataAdapter インスタンスなどの ADO.NET オブジェクトを作成するためのコードが含まれています。 データ アクセス コードを使用して、Oracle データベースなどの他のデータ ストアからサブスクライバーの一覧を取得することはできません。 ページ開発者は、新しいデータ ストアにアクセスする必要があるたびに、(GetSubscribers メソッドを使用して) データ アクセス コードを変更する必要があります。 次に、ADO.NET 2.0 でプロバイダー パターンを使用して、ページ開発者が汎用データ アクセス コードを記述してさまざまな種類のデータ ストアにアクセスできるようにする方法について説明します。

ADO.NET 2.0 のプロバイダー パターン

GetSubscribers メソッドの主な問題は、ADO.NET オブジェクトを作成するためのコードが含まれていることです。 プロバイダー パターンに従って、データ アクセス コードは、ADO.NET オブジェクトを作成するためのコードを別のクラスに提供する責任を委任する必要があります。 このクラスは、ADO.NET オブジェクトを作成するためのコードを提供するため、このクラスを "コード プロバイダー クラス" と見なします。 コード プロバイダー クラスは、CreateConnection、CreateCommand、CreateDataAdapterなどのメソッドを公開します。各メソッドは、対応する ADO.NET オブジェクトを作成するためのコードを提供します。

コード プロバイダー クラスには実際のコードが含まれるため、同じクラスを使用して異なるデータ ストアにアクセスすることはできません。 そのため、データ アクセス コード (GetSubscribers メソッド) は、新しいデータ ストアへのアクセスに使用されるたびに新しいコード プロバイダー クラスにコードを提供する責任を委任するように変更および再構成する必要があります。 GetSubscribers メソッドは、コードが含まれていない場合でも、コードに関連付けられています。

プロバイダー パターンは、この問題の解決策を提供し、次の手順で構成されます。

  1. 抽象基本プロバイダー クラスを設計して実装します。

  2. 抽象基本プロバイダー クラスからすべてのコード プロバイダー クラスを派生させます。

  3. 個々のコード プロバイダー クラスではなく抽象基底クラスを使用するデータ アクセス コード (GetSubscribers メソッド) を用意します。

    抽象基底クラスは、ADO.NET オブジェクトを作成するためのコードを適切なサブクラスに提供する責任を委任します。 抽象基底クラスの名前は、DbProviderFactoryです。 このクラスのメソッドの一部を次に示します。

    public abstract class DbProviderFactory
    {
            public virtual DbConnection CreateConnection();
            public virtual DbCommand CreateCommand();
            public virtual DbDataAdapter CreateDataAdapter();
    }
    

    各サブクラスは、特定のデータ ストアに適切な ADO.NET オブジェクトを作成するためのコードを提供します。 たとえば、SqlClientFactory サブクラスは、図 2 に示すように、Microsoft SQL Server にアクセスする ADO.NET オブジェクトを作成するためのコードを提供します。

    図 2. SqlClientFactory クラスとその一部のメソッド

    public class SqlClientFactory : DbProviderFactory
    {
            public override DbConnection CreateConnection()
            {
                    return new SqlConnection();
            }
    
           public override DbCommand CreateCommand()
           {
                    return new SqlCommand();
           }
    
           public override DbDataAdapter CreateDataAdapter()
           {
                 return new SqlDataAdapter();
           }
    }
    

プロバイダー パターンを使用すると、すべてのサブクラスが同じ基底クラスのサブクラスであるため、データ アクセス コードですべてのサブクラスを同じように処理できます。 データ アクセス コードに関する限り、すべてのサブクラスは DbProviderFactory 型です。 データ アクセス コードには、使用されているサブクラスの特定の型を知る方法はありません。 これにより、新しい問題が発生します。 データアクセスコード(GetSubscribersメソッド)がサブクラスの型を知らない場合、サブクラスのインスタンスをどのようにインスタンス化できますか?

この問題に対するプロバイダー パターン ソリューションは、次の 3 つの部分で構成されます。

  1. 一意の文字列は、各サブクラスを識別するために使用されます。 ADO.NET 2.0 では、サブクラスの名前空間を一意の文字列 ID として使用します。たとえば、一意の文字列 ID の System.Data.SqlClientSystem.Data.OracleClient は、それぞれ SqlClientFactory と OracleClientFactory サブクラス 識別します。
  2. テキスト ファイル (通常は XML ファイル) は、すべてのサブクラスに関する情報を格納するために使用されます。 ADO.NET 2.0 では、machine.config ファイルと web.config ファイルを使用して必要な情報が格納されます。 サブクラスに関する情報には、特に一意の文字列 ID とサブクラスの型の名前が含まれています。 たとえば、SqlClientFactory サブクラスに関する情報には、System.Data.SqlClient 一意の文字列 ID とサブクラスの型の名前 (System.Data.SqlClient.SqlClientFactory) が含まれます。
  3. 静的メソッドは設計および実装されています。 このメソッドは、抽象基底クラスの一部または別のクラスの一部である可能性があります。 ADO.NET 2.0 では、GetFactory 静的メソッドを公開する、DbProviderFactories という名前の別のクラスが使用されます。 メソッドは、目的のサブクラスの一意の文字列 ID を唯一の引数として受け取り、指定された一意の文字列 ID を持つサブクラスを machine.config ファイル内で検索します。メソッドは、目的のサブクラスの型の名前を抽出し、リフレクションを使用してサブクラスのインスタンスを動的に作成します。

データ アクセス コード (GetSubscribers メソッド) は、GetFactory 静的メソッドを呼び出し、対応するサブクラスのインスタンスにアクセスするための適切な一意の文字列 ID を渡します。 GetSubscribers メソッドは、インスタンスにアクセスした後、CreateConnection()、CreateCommand() などの適切な作成メソッドを呼び出して、適切な ADO.NET オブジェクトをインスタンス化します (図 3 を参照)。

図 3. 新しい ADO.NET プロバイダー パターンを使用する GetSubscribers メソッドのバージョン

public IEnumerable GetSubscribers()
{
    DbProviderFactory provider = DbProviderFactories.GetFactory("System.Data.SqlClient");
    DbConnection con = provider.CreateConnection();
    con.ConnectionString = @"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf";
    DbCommand com = provider.CreateCommand();
    com.Connection = con;
    com.CommandText = "Select * From Subscribers";
    com.CommandType = CommandType.Text;

    DataSet ds = new DataSet();
    DbDataAdapter ad = provider.CreateDataAdapter();
    ad.SelectCommand = com;

    con.Open();
    ad.Fill(ds);
    con.Close();

    return ds.Tables[0].DefaultView;
}

データ アクセス コード (GetSubscribers メソッド) は、ADO.NET オブジェクトを作成するためのコードを、GetFactory メソッドがインスタンス化して返すコード プロバイダー クラス インスタンスに提供する役割を委任します。 そのため、同じデータ アクセス コードを使用して、Microsoft SQL Server や Oracle などのさまざまなデータ ストアにアクセスできます。

適切な ADO.NET オブジェクトを作成するためのコードは、データ ストア固有です。 ADO.NET 2.0 のプロバイダー パターンは、データ アクセス コード (GetSubscribers メソッド) からこれらのデータ ストア固有の部分を削除して、より汎用的にします。 ただし、プロバイダー パターンでは、データ ストア固有の部分がすべて削除されるわけではありません。 GetSubscribers メソッドを詳しく調べると、次の残りのデータ ストア固有の部分が明らかになります。

  1. 接続文字列
  2. 基になるコード プロバイダー クラスを識別する一意の文字列 ID
  3. コマンド テキスト
  4. コマンドの種類

上記の部分について何かが行われなければ、データ アクセス コードは引き続き特定の種類のデータ ストアに関連付けられます。 ADO.NET 2.0 のプロバイダー パターンは、この問題には役立たない。 ただし、ADO.NET 2.0 には、最初の 2 つのデータ ストア固有の部分 (接続文字列や一意の文字列 ID など) を GetSubscribers メソッドから削除するための他のツールと手法が用意されています。

接続文字列

接続文字列は、Web アプリケーションで最も重要なリソースの一部です。 これらは非常に重要であるため、.NET Framework 2.0 では"一流の市民" として扱われます。 web.config ファイルでは、アプリケーションで使用されるすべての接続文字列 <含む connectionStrings> という名前の新しいセクションがサポートされるようになりました。 そのため、接続文字列を GetSubscribers メソッドからこのセクションに移動します。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
          <add 
            name="MySqlConnectionString" 
            connectionString="Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"
      providerName="System.Data.SqlClient"/>
    </connectionStrings>
</configuration>

<は、<connectionStrings> 要素の> サブ要素を追加して、次の 3 つの重要な属性を公開します。

  • 名前— 接続文字列のフレンドリ名
  • connectionString—実際の接続文字列
  • providerName—コード プロバイダー クラスの一意の文字列 ID または不変

NET Framework 2.0 では、次に示すように、データ アクセス コード (GetSubscribers メソッド) に適切なツールが提供され、web.config ファイルから接続文字列の値を汎用的に抽出できます。 .NET Framework 2.0 の System.Configuration 名前空間には、Configuration という名前の新しいクラスが含まれています。 このクラスは、web.config または machine.config ファイルのコンテンツ全体を表します。 データ アクセス コードでは、新しい演算子を使用してこのクラスのインスタンスを直接作成することはできません。

クラス自体は、web.config ファイルへのパスを受け取り、web.config ファイルの内容全体を表す Configuration クラスのインスタンスを返す、GetWebConfiguration という名前の静的メソッドを公開します。

Configuration configuration = Configuration.GetWebConfiguration("~/");

ConfigurationSection クラスから継承するクラスは、web.config ファイルのすべてのセクションを表します。 クラスの名前は、セクションの名前とキーワード Section で構成されます。 たとえば、ConnectionStringsSection クラスは、web.config ファイルの <connectionStrings> セクションの内容を表します。 データ アクセス コード (GetSubscribers メソッド) は、新しい演算子を使用して ConnectionStringsSection クラスのインスタンスを直接作成することはできません。 Configuration クラスは、web.config ファイルのさまざまなセクションを表すすべてのオブジェクトを含む Sections という名前のコレクション プロパティを公開します。

ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];

Sections は ConfigurationSection オブジェクトのコレクションであるため、データ アクセス コードは、返されたインスタンスを型キャストする必要があります。 GetSubscribers メソッドが ConnectionStringsSection オブジェクトにアクセスし、それを使用して接続文字列の値にアクセスした後:

string connectionString = section.ConnectionStrings["MySqlConnectionString"].ConnectionString;

図 4 は、接続文字列を一般的に抽出するために必要なコードを含む GetSubscribers メソッドの新しいバージョンを示しています。

図 4. web.config ファイルから接続文字列を抽出する GetSubscribers メソッドのバージョン

public IEnumerable GetSubscribers()
{
    DbProviderFactory provider = DbProviderFactories.GetFactory("System.Data.SqlClient");
    DbConnection con = provider.CreateConnection();

    Configuration configuration = Configuration.GetWebConfiguration("~/");
    ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];
    con.ConnectionString = section.ConnectionStrings["MySqlConnectionString"].ConnectionString;

    DbCommand com = provider.CreateCommand();
    com.Connection = con;
    com.CommandText = "Select * From Subscribers";
    com.CommandType = CommandType.Text;

    DataSet ds = new DataSet();
    DbDataAdapter ad = provider.CreateDataAdapter();
    ad.SelectCommand = com;

    con.Open();
    ad.Fill(ds);
    con.Close();

    return ds.Tables[0].DefaultView;
}

GetSubscribers メソッドには MySqlConnectionString 文字列が含まれるようになったため、Oracle データベースなどの別のデータ ストアのサポートを追加するには、GetSubscribers メソッドを変更する必要があります。 私たちは正方形に戻っているようです。 いやそうではありません。 接続文字列をデータ アクセス コードから web.config ファイルに移動することで、いくつかの重要な利点が得られます。

  • 接続文字列は、web.config ファイルの connectionStrings 要素のサブ要素 (XML ドキュメント)> 追加 <の connectionString 属性の値になりました。 XML ドキュメントの優れた点は、ドキュメント内の 1 つの要素を暗号化できることです。 ドキュメントの一部のみを保護する必要がある場合は、ドキュメント全体を暗号化する必要はありません。 .NET Framework 2.0 には、最も重要なリソースである接続文字列を保護するために、<connectionStrings> セクションを暗号化できるツールが付属しています。 ハッカーが接続文字列を手に入れた場合に、ハッカーが貴重なデータベースにどれだけの損害を与えることができるかを想像してみてください。 接続文字列はすべて、ハッカーがデータベースにアクセスするために必要であることを覚えておいてください。

  • 私たちが行ったことは、次の文字列を置き換えることだと思われるかもしれません

    "Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"
    

    を新しい文字列 MySqlConnectionString で指定します。 ただし、大きな違いは 1 つあります。 前者の文字列には、Oracle などの別のデータベースには適用されない SQL Server データベース固有の情報が含まれていますが、後者の文字列は単なるフレンドリ名です。

ただし、フレンドリ名は、web.config ファイルの <connectionStrings> セクション内の特定の接続文字列を参照するため、引き続き問題が発生する可能性があります。 この例では、Microsoft SQL Server へのアクセスに使用される接続文字列を参照しています。 つまり、Oracle などの別のデータ ストアにアクセスするには、別のフレンドリ名を使用するように GetSubscribers メソッド (データ アクセス コード) を変更する必要があります。

データ アクセス コードの変更を回避するために、データ アクセス コードから web.config ファイルの <appSettings> セクションにフレンドリ名を移動し、次のように実行時にデータ アクセス コードに動的に抽出させることができます。

string connectionStringName = ConfigurationSettings.AppSettings["ConnectionStringName"];

また、プロバイダー名を <appSettings> セクションに移動します。

string providerName = ConfigurationSettings.AppSettings["ProviderName"];

ページ開発者は、<の 属性を変更するだけで、><appSettings> 要素のサブ要素を同じデータ アクセス コードに追加し、データ アクセス コード自体を変更せずに別のデータ ストアにアクセスできます。

図 5 は、最近の変更を含むデータ アクセス コード (GetSubscribers メソッド) のバージョンを示しています。

図 5. web.config ファイルからプロバイダー名と接続文字列のフレンドリ名を抽出する GetSubscribers メソッドのバージョン

public IEnumerable GetSubscribers()
{
    string connectionStringName = ConfigurationSettings.AppSettings["ConnectionStringName"];
    string providerName = ConfigurationSettings.AppSettings["ProviderName"];
    Configuration configuration = Configuration.GetWebConfiguration("~/");
    ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];

    

    DbProviderFactory provider = DbProviderFactories.GetFactory(providerName);
    DbConnection con = provider.CreateConnection();
    con.ConnectionString = section.ConnectionStrings[connectionStringName].ConnectionString;

    DbCommand com = provider.CreateCommand();
    com.Connection = con;
    com.CommandText = "Select * From Subscribers";
    com.CommandType = CommandType.Text;

    DataSet ds = new DataSet();
    DbDataAdapter ad = provider.CreateDataAdapter();
    ad.SelectCommand = com;

    con.Open();
    ad.Fill(ds);
    con.Close();

    return ds.Tables[0].DefaultView;
}

データ ソース コントロール

図 5 に示すように、GetSubscribers メソッドの最新バージョンは、次の問題のため一般的ではありません。

  • このメソッドには、データ ストア (コマンド テキストとコマンドの種類など) 固有の情報が含まれています。 そのため、ページ開発者は、メソッドを使用して別のデータベースにアクセスする前に、メソッドを変更する必要があります。
  • メソッドは、DataView クラスのインスタンスを呼び出し元に返し、メソッドが呼び出し元の表形式データをフィードできるようにします。 これは、GetSubscribers メソッドとその呼び出し元の間のコントラクトと考えることができます。 呼び出し元は、基になるデータ ストア自体が表形式でない場合でも、すべての状況で GetSubscribers メソッドがコントラクトを受け入れると想定しています。 GetSubscribers メソッドは ADO.NET オブジェクトを使用して基になるデータ ストアにアクセスするため、XML ファイルなどの階層データ ストアにアクセスできません。ここでは、System.Xml 内のクラスとそのサブ名前空間のインスタンスを ADO.NET オブジェクトではなく使用する必要があります。

GetSubscribers メソッドで使用されるデータ アクセス コードの主な問題は、基になるデータ ストアからデータを抽出する実際のコードが直接含まれていることです。 これは、.NET Framework 2.0 プロバイダー パターンが具体的に解決するように設計されている問題の種類です。 プロバイダー パターンに従って、GetSubscribers メソッドは、データ ストアにアクセスするためのコードを別のクラスに提供する責任を委任する必要があります。 コード プロバイダー クラスと呼ばれます。 コード プロバイダー クラスの種類は、アクセスするデータ ストアの種類によって異なります。 これらのコード プロバイダー クラスは、まとめてデータ ソース コントロールと呼ばれます。 ASP.NET 2.0 には、SqlDataSourceAccessDataSourceObjectDataSourceXmlDataSource、SiteMapDataSource コントロール など、さまざまな種類のデータ ソース コントロールが用意されています。

SqlDataSource コントロールは、Microsoft SQL Server や Oracle などのリレーショナル データ ストアからデータを更新、削除、挿入、抽出するように特別に設計されています。 AccessDataSource コントロールは、Microsoft Access データベースの操作方法を認識する SqlDataSource コントロールのサブクラスです。 一方、ObjectDataSource コントロールは、メモリ内ビジネス オブジェクトをデータ ストアとして使用します。 XmlDataSource コントロールは、XML ドキュメントからデータを抽出するように特別に設計されています。 ただし、XmlDataSource コントロールは、基になる XML ドキュメントへの書き込みアクセスを提供しません。

各データ ソース コントロールは、基になるデータ ストアの 1 つ以上のビューを公開します。 各ビューは、適切なクラスのインスタンスです。 たとえば、SqlDataSource、AccessDataSource、および ObjectDataSource コントロールは、それぞれ、SqlDataSourceViewAccessDataSourceView、および ObjectDataSourceView クラスのインスタンスであるビューを公開します。 ビューでは、基になるデータ ストアの実際の型が非表示になり、データ アクセス コードで想定される型のように動作します。 たとえば、GetSubscribers メソッドは、クライアントに表形式のデータをフィードするため、表形式のデータ ストアを想定しています。 表形式ビューを使用すると、GetSubscribers メソッドは、データ ストア自体が XML ドキュメントなどの階層データ ソースである場合でも、基になるデータ ストアから表形式データを抽出できます。 これにより、GetSubscribers メソッドは、表形式データ ストアと階層データ ストアの両方を表形式のデータ ストアとして扱います。

データ ソース コントロールは、表形式と階層型の 2 種類のビューをクライアントに提供できます。 ASP.NET 2.0 には、XmlDataSource と SiteMapDataSource の両方の種類のビューを提供する 2 つのデータ ソース コントロールが付属しています。 残りのデータ ソース コントロール (SqlDataSource、AccessDataSource、ObjectDataSource) には、表形式のビューのみが表示されます。 ただし、表形式ビューと階層ビューの両方を提供するように拡張できます。

表形式データ ソース コントロール

表形式データ ソース コントロールは、データ ストアが表形式であるかどうかに関係なく、基になるデータ ストアを表形式のデータ ストアのように動作させます。 表形式のデータ ストアは、各行がデータ項目を表す行と列のテーブルで構成されます。 テーブルの名前は、表形式データ ストア内の他のテーブル間で一意に識別され、そのテーブルを検索します。 表形式ビューはテーブルのように機能します。つまり、ビューには名前が付けられます。

前に説明したように、各データ ソース管理クラスとそれに関連付けられているビュー クラス (たとえば、SqlDataSource クラスとそれに関連付けられている SqlDataSourceView クラス) は、基になるデータ ストアからデータを更新、削除、挿入、および抽出するための実際のコードを提供します。 明らかに、各データ ソース コントロールとそれに関連付けられているビュー クラスのコードは、特定の種類のデータ ストアを操作するように特別に設計されています。 そのため、各データ ソース管理クラスとそれに関連付けられているビュー クラスは、データ ストア固有です。 これは、データ ソース コントロールとその表形式ビューを使用して基になるデータ ストアにアクセスする GetSubscribers メソッドに重大な問題を引き起こします。これは、メソッドを特定の種類のデータ ストアに結び付けるので、同じメソッドを使用して異なる種類のデータ ストアにアクセスできないことを意味します。

ASP.NET 2.0 では、プロバイダー パターンを使用して次のことが行われるソリューションが提供されます。

  1. IDataSource インターフェイスと DataSourceView 抽象クラス について説明します
  2. IDataSource インターフェイスからすべての表形式データ ソース コントロールを派生させる
  3. DataSourceView 抽象クラスからすべての表形式ビューを派生させる

IDataSource インターフェイスと DataSourceView 抽象クラスは、データ ストアから適切なサブクラスにデータを更新、削除、挿入、抽出するための実際のコードを提供する役割を委任します。 GetSubscribers メソッドなどのデータ アクセス コードでは、IDataSource インターフェイスと DataSourceView 抽象クラスのメソッドとプロパティを使用する必要があります。 SqlDataSource などの特定のデータ ソース管理クラスまたは SqlDataSourceView などの特定のデータ ソース ビュー クラスに固有のメソッドまたはプロパティを使用することはできません。 プロバイダー パターンを使用すると、データ アクセス コードは、すべてのデータ ソース コントロールとそれぞれのデータ ソース ビューを一般的な方法で処理できます。 データ アクセス コードに関する限り、すべてのデータ ソース コントロールは IDataSource 型で、すべてのデータ ソース ビューは DataSourceView 型です。 データ アクセス コードには、使用されているデータ ソース コントロールとデータ ソース ビュー オブジェクトの実際の型を知る方法はありません。 これにより、新しい問題が発生します。 データ アクセス コード (GetSubscribers メソッド) がデータ ソース コントロールの型を認識していない場合、そのインスタンスをインスタンス化するにはどうすればよいですか?

前に説明したように、プロバイダー パターンには、次の手順で構成されるソリューションが用意されています。

  1. 一意の文字列 ID は、各データ ソース管理クラスを識別するために使用されます。
  2. テキスト ファイル (通常は XML ファイル) は、すべてのデータ ソース管理クラスに関する情報を格納するために使用されます。
  3. 特定の文字列 ID を持つサブクラスを XML ファイルで検索するメカニズムが設計および実装されています。

次に、ASP.NET 2.0 がプロバイダー パターンの上記の 3 つのタスクを実装する方法を見てみましょう。 ASP.NET 2.0 では、Control クラスからすべてのデータ ソース コントロールが派生します。 HTML マークアップ テキストをレンダリングしない場合、データ ソース コントロールが Control クラスから派生する理由 次の 3 つの重要な機能を継承できるように、Control クラスから派生します。

  1. これらは宣言によってインスタンス化できます。
  2. ポスト バック間でプロパティ値を保存および復元します。
  3. これらは、含まれているページのコントロール ツリーに追加されます。

最初の機能を使用すると、ページ開発者は、それぞれの.aspx ファイル内のデータ ソース コントロールを宣言によってインスタンス化できます。 したがって、.aspx ファイルは、プロバイダー パターンの 2 番目の手順で必要なテキストまたは XML ファイルとして機能します。 ASP.NET 2.0 コントロール アーキテクチャは、宣言されたデータ ソース コントロールのインスタンスを動的に作成し、そのインスタンスを、宣言されたデータ ソース コントロールの ID プロパティの値である名前の変数に割り当てます。 これは、プロバイダー パターンに必要な上記の最初と 3 番目の手順を処理します。

図 6 は、IDataSource インターフェイスと DataSourceView 抽象クラスのメソッドとプロパティを使用して基になるデータ ストアにアクセスする GetSubscribers メソッドのバージョンを示しています。

図 6. IDataSource インターフェイスと DataSourceView 抽象クラスのメソッドとプロパティを使用する GetSubscribers メソッドのバージョン

void GetSubscribers()
{
    IDataSource ds = (IDataSource)MySource;
    DataSourceView dv = ds.GetView(String.Empty);
    DataSourceSelectArguments args = new DataSourceSelectArguments();
    if (dv.CanSort)
        args.SortExpression = "Email";
    DataSourceViewSelectCallback callback = new DataSourceViewSelectCallback(SendMail);
    dv.Select(args, callback);
}

GetSubscribers メソッドの最初の行は、メソッドがデータ ソース コントロールを IDataSource 型のオブジェクトとして扱うことを明確に示しています。 このメソッドは、データ ソース コントロールが SqlDataSource、AccessDataSource、ObjectDataSource、XmlDataSource コントロールのいずれであるかなど、データ ソース コントロールの実際の型を考慮する必要はありません。 これにより、ページ開発者は、データ アクセス コード (GetSubscribers メソッド) を変更しなくても、データ ソースコントロールから別のデータ ソースコントロールに切り替えることができます。 次のセクションでは、この重要な問題について詳しく説明します。

GetSubscribers メソッドは、IDataSource オブジェクトの GetView メソッドを呼び出して、既定の表形式ビュー オブジェクトにアクセスします。 GetView メソッドは DataSourceView 型のオブジェクトを返します。 GetSubscribers メソッドは、ビュー オブジェクトが SqlDataSourceView、AccessDataSourceView、ObjectDataSourceView、XmlDataSourceView オブジェクトのいずれであるかなど、ビュー オブジェクトの実際の型を考慮する必要はありません。

次に、GetSubscribers メソッドは、DataSourceSelectArguments クラスのインスタンスを作成して、Select 操作が返すデータの挿入、ページング、合計行数の取得などの追加の操作を要求します。 このメソッドは、最初に、DataSourceView クラスの CanInsertCanPageまたは CanRetrieveTotalRowCount プロパティの値を確認して、ビュー オブジェクトが要求を行う前にそれぞれの操作をサポートしていることを確認する必要があります。

Select 操作は非同期であるため、GetSubscribers メソッドは SendMail メソッドをコールバックとして登録します。 Select メソッドは、データに対してクエリを実行し、データを引数として渡した後、SendMail メソッドを自動的に呼び出します (図 7 を参照)。

図 7. SendMail メソッドは、データを列挙し、必要な情報を抽出します。

void SendMail(IEnumerable data)
{
    string firstName = String.Empty;
    string lastName = String.Empty;
   
    IEnumerator iter = data.GetEnumerator();
    while (iter.MoveNext())
    {
        MailMessage message = new MailMessage();
        message.From = "admin@greatnews.com";
        message.To = DataBinder.Eval(iter.Current, "Email").ToString();
        message.Subject = "NewsLetter";
        firstName = DataBinder.Eval(iter.Current, "FirstName").ToString();
        lastName = DataBinder.Eval(iter.Current, "LastName").ToString();
        string mes = "Dear " + firstName + " " + lastName + ",<br/>";
        mes += MessageBody.Text;
        message.Body = mes;
        message.BodyFormat = MailFormat.Html;
      SmtpMail.SmtpServer = "<myserver>";
      SmtpMail.Send(message);
    }
}

SendMail メソッドは、最初の引数として渡されたオブジェクトの GetEnumerator メソッドを呼び出して列挙子オブジェクトにアクセスし、列挙子を使用してデータを列挙します。 SendMail メソッドは、DataBinder クラスの Eval メソッドを使用して、各サブスクライバーの電子メール アドレス、名、姓を抽出し、各サブスクライバーにニュース レターを送信します。

データ ソース管理間の切り替え

前に説明したように、ASP.NET コントロール アーキテクチャは、それぞれの.aspx ページで宣言されているデータ ソース コントロールのインスタンスを動的に作成し、宣言されたデータ ソース コントロールの ID プロパティの値である名前の変数に割り当てます。 このように宣言されたデータ ソース コントロールを動的にインスタンス化すると、GetSubscribers メソッドがデータ ソース コントロールの実際の型から分離され、メソッドはすべてのデータ ソース コントロールを IDataSource 型のオブジェクトとして処理できます。 これにより、ページ開発者は、データ アクセス コード (GetSubscribers メソッド) を変更せずに、データ ソースコントロールの種類を切り替えることができます。 このセクションでは、このような場合の例を示します。

Web アプリケーションでは、GetSubscribers メソッドを ObjectDataSource コントロールと組み合わせて使用して、Microsoft SQL Server などのリレーショナル データ ストアからサブスクライバーの一覧を取得するとします。

<asp:SqlDataSource ID="MySource" Runat="Server"
ConnectionString="<%$ ConnectionStrings:MySqlConnectionString %>"
SelectCommand="Select * From Subscribers" />

Web アプリケーションが、サブスクライバーの一覧が XML ドキュメントから取得される可能性がある環境で動作するとします。 XML ドキュメントは、ローカル XML ファイルまたは URL 経由でアクセスされるリモート リソースである可能性があります。 そのため、アプリケーションは XML ドキュメントからサブスクライバーの一覧を取得できる必要があります。 当然、ObjectDataSource コントロールは XML ドキュメントから表形式データを取得するようには設計されていません。つまり、XmlDataSource コントロールなどの XML ドキュメントから表形式データを取得できるデータ ソース コントロールを使用する必要があります。

GetSubscribers メソッドは、ObjectDataSource クラスと ObjectDataSourceView クラスに固有のプロパティやメソッドを使用せず、IDataSource インターフェイスと DataSourceView 抽象クラスのメソッドとプロパティのみを使用してデータ ソース コントロールを処理します。 ObjectDataSource から XmlDataSource コントロールに簡単に切り替え、GetSubscribers メソッドと同じデータ アクセス コードを使用してサブスクライバーの一覧を取得できます。

<asp:XmlDataSource ID="MySource" Runat="Server"
      DataFile="data.xml" XPath="/Subscribers/Subscriber" />

すべてのサブスクライバーを選択するには、XmlDataSource コントロールの XPath 属性の値を /Subscriber/Subscriber に設定します。

挿入および削除操作

Web アプリケーションは 2 つの部分で構成されています。 アプリケーションの 2 番目の部分では、ユーザーはメーリング リストのサブスクライブ/サブスクライブ解除を行うことができます。 図 8 に示すように、Subscribe メソッドは、[サブスクライブ] ボタンがクリックされたときに呼び出されます。

図 8. Subscribe メソッドは、[サブスクライブ] ボタンがクリックされたときに呼び出されます。

void Subscribe(Object sender, EventArgs e)
{
    IDataSource ds = (IDataSource)MySource;
    DataSourceView dv = ds.GetView(String.Empty);
    KeyedList values = new KeyedList();
    values.Add("Email", Email.Text);
    values.Add("FirstName", FirstName.Text);
    values.Add("LastName", LastName.Text);
    DataSourceViewOperationCallback callback = new DataSourceViewOperationCallback(OperationCallback);
    if (dv.CanInsert)
        dv.Insert(values, callback);
}

Subscribe メソッドの最初の行は、メソッドがデータ ソース コントロールの実際の型を気にしないことを示しています。 そのため、Subscribe メソッドのコードを変更しなくても、新しいデータ ストアをサポートするために別のデータ ソース コントロールに切り替えることができます。

このメソッドは、KeyedList クラスのインスタンスを使用して、サブスクライバーの電子メール、名、および姓を収集します。 KeyedList クラスを使用する必要はありません。 ArrayList、KeyedList など、IDictionary インターフェイスを実装する任意のクラスを使用できます。

Subscribe メソッドは、データ ソース ビュー オブジェクトの CanInsert プロパティの値をチェックして、Insert メソッドを呼び出す前に、ビュー オブジェクトが Insert 操作をサポートしていることを確認します。 Subscribe メソッドは、最初の引数として KeyedList インスタンスを Insert メソッドに渡します。

Unsubscribe メソッドは、Subscribe メソッドと同様に動作します。 主な違いは、図 9 に示すように、Unsubscribe メソッドは、それぞれのビュー オブジェクトの Delete メソッドを呼び出して、基になるデータ ストアからサブスクリプションを削除することです。

図 9. Unsubscribe ボタンがクリックされると、Unsubscribe メソッドが呼び出されます。

void Unsubscribe(Object sender, EventArgs e)
{
    IDataSource ds = (IDataSource)MySource;
    DataSourceView dv = ds.GetView(String.Empty);
    KeyedList keys = new KeyedList();
    keys.Add("Email", Email.Text);
    KeyedList oldValues = new KeyedList();
    oldValues.Add("Email", Email.Text);
    DataSourceViewOperationCallback callback = new DataSourceViewOperationCallback(OperationCallback);
    if (dv.CanDelete)
        dv.Delete(keys, oldValues, callback);
}

階層データ ソース コントロール

GetSubscribers メソッド (データ アクセス コード) は、表形式のデータを呼び出し元にフィードします。 ただし、データ アクセス コードが階層データを呼び出し元に返す必要がある場合があります。 このセクションでは、階層データを返す GetSubscribers メソッドのバージョンの実装について説明します。 これは、メソッドとその呼び出し元の間のコントラクトと考えることができます。 呼び出し元は、このメソッドが階層データ ストアと表形式データ ストアの両方から階層データを返す必要があります。

ASP.NET 2.0 では、プロバイダー パターンを使用して、基になるデータ ストアの実際の型から GetSubscribers メソッドを分離し、メソッドにデータ ストアの階層ビューを表示します。 これにより、このメソッドは階層データ ストアと表形式データ ストアの両方を階層データ ストアとして扱います。

各階層データ ソース 管理は、特定のデータ ストアを操作するように特別に設計されています。 ただし、すべての階層データ ソース コントロールは IHierarchicalDataSource インターフェイスを実装し、すべての階層データ ソース ビューは HierarchyDataSourceView クラスから派生するため、GetSubscribers メソッドは各データ ソース コントロールの詳細を処理する必要がないため、すべてのビューを汎用的な方法で処理できます。

IHierarchicalEnumerable GetSubscribers()
{
    IHierarchicalDataSource ds = (IHierarchicalDataSource)MySource;
    HierarchicalDataSourceView dv = ds.GetHierarchicalView("/Subscribers");
    return dv.Select();
}

GetSubscribers メソッドの最初の行は、メソッドがデータ ソース コントロールを IHierarchicalDataSource 型のオブジェクトとして扱い、データ ソース コントロールの実際の型を考慮しないことを示しています。 これにより、ページ開発者は、GetSubscribers メソッドのコードを変更しなくても、新しいデータ ストアのサポートを追加する新しい階層データ ソース コントロールに切り替えることができます。

次に、GetSubscribers メソッドは、HierarchyDataSourceView クラスの GetHierarchicalView メソッドを呼び出して、"/Subscribers" などの指定されたパスを持つ階層ビューにアクセスします。 Select メソッドが非同期ではないことに注意してください。 アプリケーションは、GetSubscribers メソッドから返されたデータを SendMail メソッドに渡します (図 15 を参照)。 データが IHierarchicalEnumerable型であることに注意してください。

IHierarchicalEnumerable は IEnumerable実装します。これは、GetEnumerator メソッドを公開することを意味します。 SendMail メソッドは、GetEnumerator メソッドを呼び出して、それぞれの IEnumerator オブジェクトにアクセスします。このオブジェクトは、その後、データの列挙に使用されます。 また、IHierarchicalEnumerable は、列挙オブジェクトを受け取り、それに関連付けられている IHierarchyData オブジェクトを返す GetHierarchyData という名前のメソッドも公開します。

IHierarchyData インターフェイスは、Itemという名前の重要なプロパティを公開します。これは、データ項目以外の何物でもありません。 SendMail メソッドは、XPathBinder クラスの Eval メソッドを使用して、Item オブジェクトに対する XPath 式を評価します。

図 10. SendMail メソッドは、データを列挙し、必要な情報を抽出して、各サブスクライバーにニュースレターを送信します。

void SendMail(IHierarchicalEnumerable data)
{
    string firstName = String.Empty;
    string lastName = String.Empty;

    IEnumerator iter = data.GetEnumerator();
    while (iter.MoveNext())
    {
        IHierarchyData ihdata = data.GetHierarchyData(iter.Current);
        MailMessage message = new MailMessage();
        message.From = "admin@subscribers.com";
        message.To = XPathBinder.Eval(ihdata.Item, "@Email").ToString();
        message.Subject = "NewsLetter";
        firstName = XPathBinder.Eval(ihdata.Item, "@FirstName").ToString();
        lastName = XPathBinder.Eval(ihdata.Item, "@LastName").ToString();
        string mes = "Hi " + firstName + " " + lastName + ",<br/>";
        mes += MessageBody.Text;
        message.Body = mes;
        message.BodyFormat = MailFormat.Html;
        SmtpMail.SmtpServer = "MyServer";
        SmtpMail.Send(message);
    }
}

結論

この記事では、さまざまな ASP.NET 2.0 と ADO.NET 2.0 のツールと手法を示す段階的なアプローチを使用して、ページ開発者がさまざまな種類のデータ ストアにアクセスするために使用できる汎用データ アクセス コードを記述する方法を示します。

・シャーラム・ホスラヴィ・博士は、シュルンベルガー・インフォメーション・ソリューションズ(SIS)のシニア・ソフトウェア・エンジニアです。Shahram は、ASP.NET、XML Web サービス、.NET テクノロジ、XML テクノロジ、3D コンピューター グラフィックス、HI/ユーザビリティ、デザイン パターン、および ASP.NET サーバー コントロールとコンポーネントの開発を専門としています。オブジェクト指向プログラミングに関して 10 年以上の経験があります。SQL Server や ADO.NET など、さまざまな Microsoft のツールとテクノロジを使用しています。シャーラムは、asp.netPRO マガジンの .NET および ASP.NET テクノロジに関する記事を執筆しています。