データ アクセス層を作成する (C#)
このチュートリアルでは、まず型指定されたデータセットを使用してデータ アクセス層 (DAL) を作成し、データベース内の情報にアクセスします。
はじめに
Web 開発者の作業の中心は、データを操作することです。 データを保存するためのデータベース、データを取得して変更するためのコード、データを収集して集計するための Web ページを作成します。 これは、ASP.NET 2.0 でこれらの一般的なパターンを実装するための手法について説明する、長いシリーズの最初のチュートリアルになります。 まず、型指定されたデータセットを使用したデータ アクセス層 (DAL)、カスタム ビジネス ルールを適用するビジネス ロジック層 (BLL)、共通ページ レイアウトを共有する ASP.NET ページからなるプレゼンテーション層で構成されたソフトウェア アーキテクチャを作成します。 このバックエンドの下準備が完了したら、レポートに進み、Web アプリケーションからデータを表示、集計、収集、検証する方法を示します。 これらのチュートリアルは簡潔で、プロセスを視覚的に説明するためのスクリーン ショットを豊富に含む詳細な手順を説明することを目的としています。 チュートリアルにはそれぞれ C# と Visual Basic のバージョンが用意されており、使用される完全なコードのダウンロードが含まれています (この最初のチュートリアルは非常に長いですが、残りのチュートリアルはより消化しやすい短いセクションで提供されています)。
これらのチュートリアルでは、App_Data ディレクトリに配置された Northwind データベースの Microsoft SQL Server 2005 Express Edition バージョンを使用します。 App_Data フォルダーには、データベース ファイルのほか、別のデータベース バージョンを使用する場合に備えて、データベースを作成するための SQL スクリプトも含まれています。 Northwind データベースの別の SQL Server バージョンを使用する場合は、アプリケーションの Web.config ファイルで NORTHWNDConnectionString の設定を更新する必要があります。 Web アプリケーションは、Visual Studio 2005 Professional Edition を使用して、ファイル システムベースの Web サイトプロジェクトとして構築されました。 ただし、すべてのチュートリアルは Visual Studio 2005 の無料バージョンである Visual Web Developer でも同様に機能します。
このチュートリアルでは、最初にデータ アクセス層 (DAL) を作成し、次に 2 番目のチュートリアルでビジネス ロジック層 (BLL) を作成し、3 番目のチュートリアルでページ レイアウトとナビゲーションに取り組みます。 4 番目以降のチュートリアルは、最初の 3 つのチュートリアルで作成された基礎に基づいています。 この最初のチュートリアルで取り上げる必要がある内容が多いため、Visual Studio を起動して始めましょう。
手順 1: Web プロジェクトの作成とデータベースへの接続
データ アクセス層 (DAL) を作成する前に、まず Web サイトを作成し、データベースをセットアップする必要があります。 まず、新しいファイル システムベースの ASP.NET Web サイトを作成します。 これを行うには、[ファイル] メニューに移動し、[新しい Web サイト] を選択して、[新しい Web サイト] ダイアログ ボックスを表示します。 ASP.NET Web サイト テンプレートを選択し、[場所] ドロップダウン リストを [ファイル システム] に設定し、Web サイトを配置するフォルダーを選択して、言語を C# に設定します。
図 1: 新しいファイル システムベースの Web サイトを作成します (クリックするとフルサイズの画像が表示されます)
これにより、Default.aspx ASP.NET ページと App_Data フォルダーを含む新しい Web サイトが作成されます。
Web サイトを作成したら、次の手順として、Visual Studio のサーバー エクスプローラーでデータベースへの参照を追加します。 サーバー エクスプローラーにデータベースを追加すると、Visual Studio 内からテーブル、ストアド プロシージャ、ビューなどを追加できます。 テーブル データを表示したり、手動またはクエリ ビルダーを使用してグラフィカルに独自のクエリを作成したりすることもできます。 さらに、DAL の型指定されたデータセットを作成する場合、Visual Studio が型指定されたデータセットを構築する際の元になるデータベースをポイントする必要があります。 その時点でこの接続情報を指定できますが、Visual Studio によって、サーバー エクスプローラーに既に登録されているデータベースのドロップダウン リストが自動的に設定されます。
サーバー エクスプローラーに Northwind データベースを追加する手順は、App_Data フォルダー内の SQL Server 2005 Express Edition データベースを使用するか、代わりに使用する Microsoft SQL Server 2000 または 2005 データベース サーバーのセットアップが存在するかによって異なります。
App_Data フォルダーでのデータベースの使用
接続する SQL Server 2000 または 2005 データベース サーバーが存在しない場合、または単にデータベースをデータベース サーバーに追加する必要がない場合は、ダウンロードした Web サイトの App_Data フォルダーに配置されている SQL Server 2005 Express Edition バージョンの Northwind データベース (NORTHWND.MDF) を使用できます。
App_Data フォルダーに配置されたデータベースは、サーバー エクスプローラーに自動的に追加されます。 コンピューターに SQL Server 2005 Express Edition がインストールされていると仮定すると、サーバー エクスプローラーに NORTHWND.MDF という名前のノードが表示され、これを展開してテーブル、ビュー、ストアド プロシージャなどを参照できます (図 2 を参照)。
App_Data フォルダーには、Microsoft Access .mdb ファイルを格納することもできます。このファイルは、対応する SQL Server と同様に、サーバー エクスプローラーに自動的に追加されます。 SQL Server オプションを使用しない場合は、いつでも Northwind Traders データベースとアプリをインストールして App_Data ディレクトリにドロップできます。 ただし、Access データベースは SQL Server ほど機能が充実しておらず、Web サイトのシナリオで使用されるように設計されていないことに注意してください。 さらに、35 以上のチュートリアルのうちのいくつかでは、Access ではサポートされていない特定のデータベースレベルの機能を使用します。
Microsoft SQL Server 2000 または 2005 データベース サーバーのデータベースへの接続
または、データベース サーバーにインストールされている Northwind データベースに接続することもできます。 データベース サーバーに Northwind データベースがまだインストールされていない場合は、まず、このチュートリアルのダウンロードに含まれるインストール スクリプトを実行して、データベース サーバーに追加する必要があります。
データベースをインストールしたら、Visual Studio のサーバー エクスプローラーに移動し、[データ接続] ノードを右クリックして、[接続の追加] を選択します。 サーバー エクスプローラーが表示されない場合は、[表示]/[サーバー エクスプローラー] に移動するか、Ctrl + Alt + S キーを押します。 これにより、[接続の追加] ダイアログ ボックスが表示され、接続先のサーバー、認証情報、データベース名を指定できます。 データベース接続情報を正常に構成し、[OK] ボタンをクリックすると、データベースが [データ接続] ノードの下にノードとして追加されます。 データベース ノードを展開して、テーブル、ビュー、ストアド プロシージャなどを調べることができます。
図 2: データベース サーバーの Northwind データベースへの接続を追加します
手順 2: データ アクセス層の作成
データを操作する際、データ固有のロジックをプレゼンテーション層に直接埋め込むこともできます (Web アプリケーションでは、ASP.NET ページがプレゼンテーション層を構成します)。 これは、ASP.NET ページのコード部分に ADO.NET コードを記述するか、マークアップ部分から SqlDataSource コントロールを使用する形式をとる場合があります。 どちらの場合も、この方法では、データ アクセス ロジックとプレゼンテーション層が密結合されます。 ただし、おすすめの方法は、データ アクセス ロジックをプレゼンテーション層から分離することです。 この分離された層はデータ アクセス層 (DAL) と呼ばれ、通常は別のクラス ライブラリ プロジェクトとして実装されます。 この階層化アーキテクチャのメリットは適切に文書化されており (これらの利点については、このチュートリアルの最後にある「参考資料」セクションを参照してください)、このシリーズではこのアプローチを使用します。
データベースへの接続の作成、SELECT、INSERT、UPDATE、DELETE コマンドの発行など、基になるデータ ソースに固有のすべてのコードは DAL に配置する必要があります。 プレゼンテーション層には、このようなデータ アクセス コードへの参照を含めず、代わりに、すべてのデータ要求に対して DAL を呼び出す必要があります。 データ アクセス層には、通常、基になるデータベース データにアクセスするためのメソッドが含まれています。 たとえば、Northwind データベースには、販売する製品とそれらが属するカテゴリを記録する Products および Categories テーブルがあります。 DAL には、次のようなメソッドが用意されています。
- GetCategories(): すべてのカテゴリに関する情報を返します
- GetProducts(): すべての製品に関する情報を返します
- GetProductsByCategoryID(categoryID): 指定したカテゴリに属するすべての製品を返します
- GetProductByProductID(productID): 特定の製品に関する情報を返します
これらのメソッドを呼び出すと、データベースに接続されて適切なクエリが発行され、結果が返されます。 結果が返される方法は重要です。 これらのメソッドは、データベース クエリによって設定されたデータセットまたは DataReader を単純に返すことができますが、厳密に型指定されたオブジェクトを使用してこれらの結果を返すのが理想的です。 厳密に型指定されたオブジェクトは、コンパイル時にスキーマが厳密に定義される一方、弱く型指定されたオブジェクトは実行時までスキーマが不明です。
たとえば、DataReader とデータセット (既定) は、データの設定に使用されるデータベース クエリが返す列によってスキーマが定義されるため、弱く型指定されたオブジェクトです。 弱く型指定された DataTable から特定の列にアクセスするには、次のような構文を使用する必要があります。DataTable.Rows[index]["columnName"] この例では、文字列または序数インデックスを使用して列名にアクセスする必要があるため、DataTable が弱く型指定されていることを示しています。 一方、厳密に型指定された DataTable では、各列がプロパティとして実装されており、次のようなコードになります。DataTable.Rows[index].columnName
厳密に型指定されたオブジェクトを返すには、開発者が独自のカスタム ビジネス オブジェクトを作成するか、型指定されたデータセットを使用します。 ビジネス オブジェクトは、そのプロパティが通常、ビジネス オブジェクトが表す基になるデータベース テーブルの列を反映するクラスとして、開発者によって実装されます。 型指定されたデータセットは、データベース スキーマに基づいて Visual Studio によって生成され、そのメンバーがこのスキーマに従って厳密に型指定されたクラスです。 型指定されたデータセット自体は、ADO.NET データセット、DataTable、および DataRow クラスを拡張するクラスで構成されます。 型指定されたデータセットには、厳密に型指定された DataTable に加えて、データセットの DataTable にデータを設定し、DataTable 内の変更をデータベースに反映するためのメソッドを持つクラスである TableAdapter も含まれるようになりました。
Note
型指定されたデータセットとカスタム ビジネス オブジェクトを使用する場合の長所と短所の詳細については、「データ層コンポーネントの設計と層を介したデータの受け渡し」を参照してください。
これらのチュートリアルのアーキテクチャでは、厳密に型指定されたデータセットを使用します。 図 3 は、型指定されたデータセットを使用するアプリケーションのさまざまな層間のワークフローを示しています。
図 3: すべてのデータ アクセス コードが DAL に降格されます (クリックするとフルサイズの画像が表示されます)
型指定されたデータセットと Table Adapter の作成
DAL の作成を開始するには、まず、型指定されたデータセットをプロジェクトに追加します。 これを行うには、ソリューション エクスプローラーでプロジェクト ノードを右クリックし、[新しい項目の追加] を選択します。 テンプレートの一覧からデータセット オプションを選択し、Northwind.xsd という名前を付けます。
図 4: プロジェクトに新しいデータセットを追加します (クリックするとフルサイズの画像が表示されます)
[追加] をクリックした後、データセットを App_Code フォルダーに追加するように求められたら、[はい] を選択します。 その後、型指定されたデータセットのデザイナーが表示され、TableAdapter 構成ウィザードが起動し、最初の TableAdapter を型指定されたデータセットに追加できます。
型指定されたデータセットは、厳密に型指定されたデータのコレクションとして機能します。厳密に型指定された DataTable インスタンスで構成され、それぞれが厳密に型指定された DataRow インスタンスで構成されます。 このチュートリアル シリーズで使用する基になるデータベース テーブルごとに、厳密に型指定された DataTable を作成します。 まず、Products テーブルの DataTable を作成しましょう。
厳密に型指定された DataTable には、基になるデータベース テーブルからデータにアクセスする方法に関する情報は含まれていないことに注意してください。 DataTable に設定するデータを取得するには、データ アクセス層として機能する TableAdapter クラスを使用します。 Products DataTable の場合、TableAdapter には、プレゼンテーション層から呼び出す GetProducts()、GetProductByCategoryID(categoryID) などのメソッドが含まれます。 DataTable は、レイヤー間でデータを渡すために使用される厳密に型指定されたオブジェクトとしての役割を果たします。
TableAdapter 構成ウィザードでは、まず、作業するデータベースの選択を求められます。 ドロップダウン リストには、サーバー エクスプローラー内の該当するデータベースが表示されます。 Northwind データベースをサーバー エクスプローラーに追加していない場合は、この時点で [新しい接続] ボタンをクリックして追加できます。
図 5: ドロップダウン リストから Northwind データベースを選択します (クリックするとフルサイズの画像が表示されます)
データベースを選択して [次へ] をクリックすると、接続文字列を Web.config ファイルに保存するかどうかを確認するメッセージが表示されます。 接続文字列を保存すると、TableAdapter クラスでハード コーディングされるのを防ぐことができます。これにより、今後接続文字列情報が変更された場合の作業が簡略化されます。 接続文字列を構成ファイルに保存する場合は、<connectionStrings> セクションに配置され、IIS GUI 管理ツール内の新しい ASP.NET 2.0 プロパティ ページを使用し、必要に応じて暗号化してセキュリティを強化したり、後で変更したりできます。この機能は、管理者にとってより理想的です。
図 6: 接続文字列を Web.config に保存します (クリックするとフルサイズの画像が表示されます)
次に、最初の厳密に型指定された DataTable のスキーマを定義し、厳密に型指定されたデータセットにデータを設定するときに TableAdapter で使用する最初のメソッドを指定する必要があります。 これら 2 つの手順は、DataTable に反映するテーブルの列を返すクエリを作成することによって同時に実行されます。 ウィザードの最後で、このクエリにメソッド名を付けます。 この操作が完了したら、このメソッドをプレゼンテーション層から呼び出すことができます。 このメソッドは、定義されたクエリを実行し、厳密に型指定された DataTable を設定します。
SQL クエリの定義を開始するには、まず TableAdapter でクエリを発行する方法を指定する必要があります。 アドホック SQL ステートメントの使用、新しいストアド プロシージャの作成、または既存のストアド プロシージャの使用を行うことができます。 これらのチュートリアルでは、アドホック SQL ステートメントを使用します。
図 7: アドホック SQL ステートメントを使用してデータのクエリを実行します (クリックするとフルサイズの画像が表示されます)
この時点で、手動で SQL クエリを入力できます。 TableAdapter で最初のメソッドを作成するときは、通常、対応する DataTable で表現する必要がある列をクエリから返すようにします。 これを実現するには、Products テーブルのすべての列とすべての行を返すクエリを作成します。
図 8: SQL クエリをテキスト ボックスに入力します (クリックするとフルサイズの画像が表示されます)
または、図 9 に示すように、クエリ ビルダーを使用してクエリをグラフィカルに構築します。
図 9: クエリ エディターを使用してクエリをグラフィカルに作成します (クリックするとフルサイズの画像が表示されます)
クエリを作成したら、次の画面に進む前に [詳細オプション] ボタンをクリックします。 Web サイト プロジェクトでは、既定で選択されている唯一の詳細オプションは [INSERT、UPDATE、および DELETE ステートメントの生成] です。クラス ライブラリまたは Windows プロジェクトからこのウィザードを実行する場合は、[オプティミスティック同時実行制御の使用] オプションも選択されます。 この時点では、[オプティミスティック同時実行制御の使用] オプションはオフのままにします。 オプティミスティック同時実行制御については、今後のチュートリアルで確認します。
図 10: [INSERT、UPDATE、および DELETE ステートメントの生成] オプションのみを選択します (クリックするとフルサイズの画像が表示されます)
詳細オプションを確認したら、[次へ] をクリックして最後の画面に進みます。 ここで、TableAdapter に追加するメソッドを選択するように求められます。 データを設定するには、次の 2 つのパターンがあります。
- DataTable にデータを格納する この方法では、DataTable をパラメーターとして受け取り、クエリの結果に基づいて DataTable にデータを設定するメソッドが作成されます。 たとえば、ADO.NET DataAdapter クラスは Fill() メソッドでこのパターンを実装します。
- DataTable を返す この方法では、メソッドが DataTable を作成して格納し、メソッドの戻り値として返します。
TableAdapter で、これらのパターンの一方または両方を実装することができます。 ここで説明したメソッドの名前を変更することもできます。 これらのチュートリアル全体を通じて後者のパターンのみを使用する場合でも、両方のチェックボックスをオンのままにしておきましょう。 また、一般的な GetData メソッドを GetProducts に変更しておきましょう。
オンにすると、最後のチェックボックス [GenerateDBDirectMethods] で、TableAdapter の Insert()、Update()、および Delete() メソッドが作成されます。 このオプションをオフのままにした場合は、TableAdapter の唯一の Update() メソッドを使用してすべての更新を実行する必要があります。このメソッドは、型指定されたデータセット、DataTable、単一の DataRow、または DataRow の配列を受け取ります (図 9 の詳細プロパティの [INSERT、UPDATE、および DELETE ステートメントの生成] オプションをオフにした場合、このチェックボックスの設定は無効になります)。このチェックボックスはオンのままにしておきましょう。
図 11: メソッド名を GetData から GetProducts に変更します (クリックするとフルサイズの画像が表示されます)
[完了] をクリックしてウィザードを終了します。 ウィザードが閉じると、先ほど作成した DataTable を表示するデータセット デザイナーに戻ります。 Products DataTable の列の一覧 (ProductID、ProductName など) と、ProductsTableAdapter のメソッド (Fill() と GetProducts()) を確認できます。
図 12: Products DataTable と ProductsTableAdapter が型指定されたデータセットに追加されます (クリックするとフルサイズの画像が表示されます)
この時点で、単一の DataTable (Northwind.Products) を含む型指定されたデータセットと、GetProducts() メソッドを含む厳密に型指定された DataAdapter クラス (NorthwindTableAdapters.ProductsTableAdapter) があります。 これらのオブジェクトを使用して、次のようなコードからすべての製品の一覧にアクセスできます。
NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
new NorthwindTableAdapters.ProductsTableAdapter();
Northwind.ProductsDataTable products;
products = productsAdapter.GetProducts();
foreach (Northwind.ProductsRow productRow in products)
Response.Write("Product: " + productRow.ProductName + "<br />");
このコードでは、データアクセス固有のコードを 1 ビットも記述する必要はありませんでした。 ADO.NET クラスのインスタンス作成や、接続文字列、SQL クエリ、ストアド プロシージャへの参照は必要ありませんでした。 代わりに、TableAdapter はローレベルのデータ アクセス コードを提供します。
この例で使用される各オブジェクトも厳密に型指定されているため、Visual Studio では IntelliSense とコンパイル時の型チェックを提供できます。 とりわけ、TableAdapter によって返される DataTable は、GridView、DetailsView、DropDownList、CheckBoxList などの ASP.NET データ Web コントロールにバインドできます。 次の例は、Page_Load イベント ハンドラー内でわずか 3 行のコードを使用して、GetProducts() メソッドによって返される DataTable を GridView にバインドする方法を示しています。
AllProducts.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="AllProducts.aspx.cs"
Inherits="AllProducts" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>View All Products in a GridView</title>
<link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<form id="form1" runat="server">
<div>
<h2>
All Products</h2>
<p>
<asp:GridView ID="GridView1" runat="server"
CssClass="DataWebControlStyle">
<HeaderStyle CssClass="HeaderStyle" />
<AlternatingRowStyle CssClass="AlternatingRowStyle" />
</asp:GridView>
</p>
</div>
</form>
</body>
</html>
AllProducts.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class AllProducts : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
ProductsTableAdapter productsAdapter = new
ProductsTableAdapter();
GridView1.DataSource = productsAdapter.GetProducts();
GridView1.DataBind();
}
}
図 13: 製品の一覧が GridView に表示されます (クリックするとフルサイズの画像が表示されます)
この例では、ASP.NET ページの Page_Load イベント ハンドラーに 3 行のコードを記述する必要がありましたが、今後のチュートリアルでは、ObjectDataSource を使用して DAL からデータを宣言的に取得する方法について検討します。 ObjectDataSource を使用すると、コードを一切記述する必要がなく、ページングと並べ替えもサポートされます。
手順 3: パラメーター化されたメソッドをデータ アクセス層に追加
この時点で、ProductsTableAdapter クラスには、データベース内のすべての製品を返す GetProducts() メソッドが 1 つしかありません。 すべての製品を操作できることは確かに便利ですが、特定の製品または特定のカテゴリに属するすべての製品に関する情報を取得する必要がある場合もあります。 このような機能をデータ アクセス層に追加するには、パラメーター化されたメソッドを TableAdapter に追加します。
GetProductsByCategoryID(categoryID) メソッドを追加してみましょう。 DAL に新しいメソッドを追加するには、データセット デザイナーに戻り、ProductsTableAdapter セクションを右クリックして [クエリの追加] を選択します。
図 14: TableAdapter を右クリックし、[クエリの追加] を選択します
最初に、アドホック SQL ステートメントを使用してデータベースにアクセスするか、新規または既存のストアド プロシージャを使用するかを確認するメッセージが表示されます。 もう一度、アドホック SQL ステートメントを使用してみましょう。 次に、使用する SQL クエリの種類を尋ねられます。 指定したカテゴリに属するすべての製品を返す必要があるため、行を返す SELECT ステートメントを記述します。
図 15: 行を返す SELECT ステートメントを作成します (クリックするとフルサイズの画像が表示されます)
次の手順では、データへのアクセスに使用する SQL クエリを定義します。 特定のカテゴリに属する製品のみを返す必要があるため、GetProducts() から同じ SELECT ステートメントを使用しますが、WHERE 句 WHERE CategoryID = @CategoryID を追加します。 @CategoryID パラメーターは、作成中のメソッドが対応する型の入力パラメーター (つまり Null 許容整数) を必要とすることを TableAdapter ウィザードに示します。
図 16: 指定したカテゴリの製品のみを返すクエリを入力します (クリックするとフルサイズの画像が表示されます)
最後の手順では、使用するデータ アクセス パターンを選択し、生成されるメソッドの名前をカスタマイズできます。 DataTable にデータを格納するパターンでは名前を FillByCategoryID に変更し、DataTable を返すパターン (GetX メソッド) では GetProductsByCategoryID を使用してみましょう。
図 17: TableAdapter のメソッドの名前を選択します (クリックするとフルサイズの画像を表示されます)
ウィザードが完了すると、データセット デザイナーに新しい TableAdapter メソッドが含まれます。
図 18: 製品のクエリをカテゴリ別に実行できるようになりました
同じ手法を使用して GetProductByProductID(productID) メソッドを追加します。
これらのパラメーター化されたクエリは、データセット デザイナーから直接テストできます。 TableAdapter でメソッドを右クリックし、[データのプレビュー] を選択します。 次に、パラメーターに使用する値を入力し、[プレビュー] をクリックします。
図 19: Beverages カテゴリに属する製品が表示されます (クリックするとフルサイズの画像が表示されます)
DAL の GetProductsByCategoryID(categoryID) メソッドを使用して、指定したカテゴリの製品のみを表示する ASP.NET ページを作成できるようになりました。 次の例は、CategoryID が 1 の Beverages カテゴリに含まれるすべての製品を示しています。
Beverages.asp
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Beverages.aspx.cs"
Inherits="Beverages" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
<link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<form id="form1" runat="server">
<div>
<h2>Beverages</h2>
<p>
<asp:GridView ID="GridView1" runat="server"
CssClass="DataWebControlStyle">
<HeaderStyle CssClass="HeaderStyle" />
<AlternatingRowStyle CssClass="AlternatingRowStyle" />
</asp:GridView>
</p>
</div>
</form>
</body>
</html>
Beverages.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class Beverages : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
ProductsTableAdapter productsAdapter = new
ProductsTableAdapter();
GridView1.DataSource =
productsAdapter.GetProductsByCategoryID(1);
GridView1.DataBind();
}
}
図 20: Beverages カテゴリの製品が表示されます (クリックするとフルサイズの画像が表示されます)
手順 4: データの挿入、更新、削除
データの挿入、更新、削除に一般的に使用されるパターンが 2 つあります。 最初に説明するデータベース ダイレクト パターンでは、呼び出されたときに 1 つのデータベース レコードを操作する INSERT、UPDATE、または DELETE コマンドをデータベースに発行するメソッドの作成が含まれます。 このようなメソッドは通常、挿入、更新、または削除する値に対応する一連のスカラー値 (整数、文字列、ブール値、DateTimes など) で渡されます。 たとえば、Products テーブルのこのパターンを使用すると、削除メソッドは削除するレコードの ProductID を示す整数パラメーターを受け取り、挿入メソッドは ProductName の文字列、UnitPrice の 10 進数、UnitsOnStock の整数などを受け取ります。
図 21: 挿入、更新、削除の各要求がデータベースにすぐに送信されます (クリックするとフルサイズの画像が表示されます)
もう 1 つのパターンであるバッチ更新パターンでは、データセット、DataTable、または DataRow のコレクション全体を 1 つのメソッド呼び出しで更新します。 このパターンでは、開発者は DataTable 内の DataRows を削除、挿入、変更してから、それらの DataRows または DataTable を更新メソッドに渡します。 このメソッドは、渡された DataRow を列挙し、(DataRow の RowState プロパティ値を使用して) 変更、追加、または削除されたかどうかを判断し、各レコードに対して適切なデータベース要求を発行します。
図 22: 更新メソッドを呼び出すとすべての変更がデータベースと同期されます (クリックするとフルサイズの画像が表示されます)
TableAdapter は既定でバッチ更新パターンを使用しますが、DB ダイレクト パターンもサポートしています。 TableAdapter の作成時に [詳細プロパティ] から [INSERT、UPDATE、および DELETE ステートメントの生成] オプションを選択したため、ProductsTableAdapter にはバッチ更新パターンを実装する Update() メソッドが含まれています。 具体的には、TableAdapter には、型指定されたデータセット、厳密に型指定された DataTable、または 1 つ以上の DataRow を渡すことができる Update() メソッドが含まれています。 TableAdapter を最初に作成したときに [GenerateDBDirectMethods] チェックボックスをオンのままにした場合、Insert()、Update()、Delete() メソッドを使用して DB ダイレクト パターンも実装されます。
どちらのデータ変更パターンでも、TableAdapter の InsertCommand、UpdateCommand、DeleteCommand プロパティを使用して、データベースに INSERT、UPDATE、DELETE コマンドを発行します。 データセット デザイナーで TableAdapter をクリックし、プロパティ ウィンドウに移動すると、InsertCommand、UpdateCommand、DeleteCommand プロパティを調べて変更できます (TableAdapter が選択されていること、およびプロパティ ウィンドウのドロップダウン リストで ProductsTableAdapter オブジェクトが選択されていることを確認してください)。
図 23: TableAdapter には InsertCommand、UpdateCommand、DeleteCommand プロパティがあります (クリックするとフルサイズの画像を表示されます)
これらのデータベース コマンド プロパティのいずれかを調べるか変更するには、クエリ ビルダーを開く CommandText サブプロパティをクリックします。
図 24: クエリ ビルダーで INSERT、UPDATE、DELETE ステートメントを構成します (クリックするとフルサイズの画像を表示されます)
次のコード例は、バッチ更新パターンを使用して、生産中止されておらず、在庫が 25 ユニット以下のすべての製品の価格を 2 倍にする方法を示しています。
NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
new NorthwindTableAdapters.ProductsTableAdapter();
// For each product, double its price if it is not discontinued and
// there are 25 items in stock or less
Northwind.ProductsDataTable products = productsAdapter.GetProducts();
foreach (Northwind.ProductsRow product in products)
if (!product.Discontinued && product.UnitsInStock <= 25)
product.UnitPrice *= 2;
// Update the products
productsAdapter.Update(products);
次のコードは、DB ダイレクト パターンを使用してプログラムによって特定の製品を削除し、更新してから新しい製品を追加する方法を示しています。
NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
new NorthwindTableAdapters.ProductsTableAdapter();
// Delete the product with ProductID 3
productsAdapter.Delete(3);
// Update Chai (ProductID of 1), setting the UnitsOnOrder to 15
productsAdapter.Update("Chai", 1, 1, "10 boxes x 20 bags",
18.0m, 39, 15, 10, false, 1);
// Add a new product
productsAdapter.Insert("New Product", 1, 1,
"12 tins per carton", 14.95m, 15, 0, 10, false);
カスタムの挿入、更新、削除メソッドの作成
DB ダイレクト メソッドで作成される Insert()、Update()、Delete() メソッドは、特に列数の多いテーブルでは少し面倒な場合があります。 前のコード例を見ると、IntelliSense を使用しないと、Products テーブルの各入力パラメーターが Update() および Insert() メソッドのどの列にマップされているかが明確ではありません。 1 つまたは 2 つの列のみを更新する場合や、新しく挿入されたレコードの IDENTITY (自動増分) フィールドの値を返す、カスタマイズされた Insert() メソッドが必要な場合があります。
このようなカスタム メソッドを作成するには、データセット デザイナーに戻ります。 TableAdapter を右クリックし、[クエリの追加] を選択し、TableAdapter ウィザードに戻ります。 2 番目の画面では、作成するクエリの種類を示すことができます。 新しい製品を追加し、新しく追加されたレコードの ProductID の値を返すメソッドを作成してみましょう。 このため、INSERT クエリを作成することにします。
図 25: Products テーブルに新しい行を追加するメソッドを作成します (クリックするとフルサイズの画像を表示されます)
次の画面に、InsertCommand の CommandText が表示されます。 クエリの末尾に同じスコープ内の IDENTITY 列に挿入された最後の ID 値を返す SELECT SCOPE_IDENTITY() を追加して、このクエリを拡張します (SCOPE_IDENTITY() の詳細と、@@IDENTITY の代わりに SCOPE_IDENTITY() を使用する理由については、技術文書を参照してください)。 SELECT ステートメントを追加する前に、INSERT ステートメントがセミコロンで終了していることを確認してください。
図 26: SCOPE_IDENTITY() 値を返すようにクエリを拡張します (クリックするとフルサイズの画像を表示されます)
最後に、新しいメソッドに InsertProduct という名前を付けます。
図 27: InsertProduct に新しいメソッド名を設定します (クリックするとフルサイズの画像を表示されます)
データセット デザイナーに戻ると、ProductsTableAdapter に新しいメソッド InsertProduct が含まれていることがわかります。 この新しいメソッドの Products テーブル内の各列にパラメーターが存在しない場合は、INSERT ステートメントがセミコロンで終了していない可能性があります。 InsertProduct メソッドを構成し、INSERT および SELECT ステートメントがセミコロンで区切られていることを確認します。
既定では、挿入メソッドはクエリ以外のメソッドを発行し、影響を受ける行数が返されます。 ただし、InsertProduct メソッドで影響を受ける行数ではなく、クエリによって返された値を返す必要があります。 これを実現するには、InsertProduct メソッドの ExecuteMode プロパティを Scalar に調整します。
図 28: ExecuteMode プロパティを Scalar に変更します (クリックするとフルサイズの画像を表示されます)
次のコードは、この新しい InsertProduct メソッドの動作を示しています。
NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
new NorthwindTableAdapters.ProductsTableAdapter();
// Add a new product
int new_productID = Convert.ToInt32(productsAdapter.InsertProduct
("New Product", 1, 1, "12 tins per carton", 14.95m, 10, 0, 10, false));
// On second thought, delete the product
productsAdapter.Delete(new_productID);
手順 5: データ アクセス層の完了
ProductsTableAdapters クラスは Products テーブルの CategoryID と SupplierID の値を返しますが、Categories テーブルの CategoryName 列と Suppliers テーブルの CompanyName 列は、製品情報を表示するときによく表示される列にもかかわらず、含まれないことに注意してください。 TableAdapter の初期メソッド GetProducts() を拡張して、CategoryName および CompanyName 列の両方の値を含めることができます。これにより、厳密に型指定された DataTable が更新され、これらの新しい列も含まれるようになります。
ただし、TableAdapter のデータの挿入、更新、削除メソッドはこの初期メソッドに基づいているため、問題が発生する可能性があります。 幸いにも、挿入、更新、削除のための自動生成メソッドは、SELECT 句のサブクエリの影響を受けません。 Categories と Suppliers にクエリをサブクエリとして追加し、JOIN として追加しないよう慎重に操作することで、データを変更するためにメソッドを再作業する必要がなくなります。 ProductsTableAdapter の GetProducts() メソッドを右クリックし、[構成] を選択します。 次に、SELECT 句を以下のように調整します。
SELECT ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
(SELECT CategoryName FROM Categories
WHERE Categories.CategoryID = Products.CategoryID) as CategoryName,
(SELECT CompanyName FROM Suppliers
WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName
FROM Products
図 29: GetProducts() メソッドの SELECT ステートメントを更新します (クリックするとフルサイズの画像を表示されます)
この新しいクエリを使用するように GetProducts() メソッドを更新すると、DataTable に 2 つの新しい列 (CategoryName と SupplierName) が追加されます。
図 30: Products DataTable に 2 つの新しい列が含まれます
GetProductsByCategoryID(categoryID) メソッドの SELECT 句も更新します。
JOIN を使用してGetProducts() SELECTを更新するとデータセット デザイナーは DB ダイレクト パターンを使用してデータベース データを挿入、更新、および削除するためのメソッドを自動生成できません。 代わりに、このチュートリアルの前半で InsertProduct メソッドで行ったのと同様に、それらを手動で作成する必要があります。 さらに、バッチ更新パターンを使用する場合は、InsertCommand、UpdateCommand、DeleteCommand の各プロパティ値を手動で指定する必要があります。
残りの TableAdapter の追加
これまでは、1 つのデータベース テーブルの単一の TableAdapter を操作する方法についてのみ説明してきました。 ただし、Northwind データベースには、Web アプリケーションで操作する必要がある関連テーブルがいくつか含まれています。 型指定されたデータセットには、複数の関連する DataTable を含めることができます。 このため、DAL を完了するには、これらのチュートリアルで使用する他のテーブルの DataTable を追加する必要があります。 型指定されたデータセットに新しい TableAdapter を追加するには、データセット デザイナーを開き、デザイナー内を右クリックし、[追加]/[TableAdapter] を選択します。 これにより、新しい DataTable と TableAdapter が作成され、このチュートリアルの前半で確認したウィザードに移動します。
次のクエリを使用して、次の TableAdapters とメソッドを作成するには数分かかります。 ProductsTableAdapter 内のクエリには、各製品のカテゴリ名とサプライヤー名を取得するためのサブクエリが含まれていることに注意してください。 さらに、ここまでの操作で、ProductsTableAdapter クラスの GetProducts() および GetProductsByCategoryID(categoryID) メソッドがすでに追加されています。
ProductsTableAdapter
GetProducts:
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT CategoryName FROM Categories WHERE Categories.CategoryID = Products.CategoryID) as CategoryName, (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName FROM Products
GetProductsByCategoryID:
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT CategoryName FROM Categories WHERE Categories.CategoryID = Products.CategoryID) as CategoryName, (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName FROM Products WHERE CategoryID = @CategoryID
GetProductsBySupplierID:
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT CategoryName FROM Categories WHERE Categories.CategoryID = Products.CategoryID) as CategoryName, (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName FROM Products WHERE SupplierID = @SupplierID
GetProductByProductID:
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT CategoryName FROM Categories WHERE Categories.CategoryID = Products.CategoryID) as CategoryName, (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName FROM Products WHERE ProductID = @ProductID
CategoriesTableAdapter
GetCategories:
SELECT CategoryID, CategoryName, Description FROM Categories
GetCategoryByCategoryID:
SELECT CategoryID, CategoryName, Description FROM Categories WHERE CategoryID = @CategoryID
SuppliersTableAdapter
GetSuppliers:
SELECT SupplierID, CompanyName, Address, City, Country, Phone FROM Suppliers
GetSuppliersByCountry:
SELECT SupplierID, CompanyName, Address, City, Country, Phone FROM Suppliers WHERE Country = @Country
GetSupplierBySupplierID:
SELECT SupplierID, CompanyName, Address, City, Country, Phone FROM Suppliers WHERE SupplierID = @SupplierID
EmployeesTableAdapter
GetEmployees:
SELECT EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo, Country FROM Employees
GetEmployeesByManager:
SELECT EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo, Country FROM Employees WHERE ReportsTo = @ManagerID
GetEmployeeByEmployeeID:
SELECT EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo, Country FROM Employees WHERE EmployeeID = @EmployeeID
図 31: 4 つの TableAdapter が追加された後のデータセット デザイナー (クリックするとフルサイズの画像を表示されます)
DAL へのカスタム コードの追加
型指定されたデータセットに追加された TableAdapter と DataTable は、XML スキーマ定義ファイル (Northwind.xsd) として表されます。 このスキーマ情報を表示するには、ソリューション エクスプローラーで Northwind.xsd ファイルを右クリックし、[コードの表示] を選択します。
図 32: Northwind の型指定されたデータセットの XML スキーマ定義 (XSD) ファイル (クリックするとフルサイズの画像を表示されます)
このスキーマ情報は、コンパイル時または実行時に (必要に応じて) C# または Visual Basic コードに変換され、この時点でデバッガーを使用してステップ実行することができます。 この自動生成されたコードを表示するには、クラス ビューに移動し、TableAdapter または型指定されたデータセット クラスの詳細を表示します。 画面にクラス ビューが表示されない場合は、[表示] メニューに移動して選択するか、Ctrl + Shift + C キーを押します。 クラス ビューから、型指定されたデータセットおよび TableAdapter クラスのプロパティ、メソッド、イベントを確認できます。 特定のメソッドのコードを表示するには、クラス ビューでメソッド名をダブルクリックするか、右クリックして [定義へ移動] を選択します。
図 33: クラス ビューから [定義へ移動] を選択し、自動生成されたコードを調べます
自動生成されたコードにより時間を大幅に節約できますが、コードは非常に汎用的があることが多く、アプリケーション固有のニーズを合わせてカスタマイズする必要があります。 自動生成されたコードを拡張するリスクは、コードを生成したツールがカスタマイズを "再生成" して上書きする必要があると判断する可能性があるということです。 .NET 2.0 の新しい部分クラスの概念により、クラスを複数のファイルに簡単に分割できます。 これにより、Visual Studio がカスタマイズを上書きすることを心配せずに、独自のメソッド、プロパティ、イベントを自動生成されたクラスに追加できます。
DAL をカスタマイズする方法を示すため、GetProducts() メソッドを SuppliersRow クラスに追加してみましょう。 SuppliersRow クラスは Suppliers テーブル内の単一のレコードを表します。各サプライヤーは 0 個以上の製品を提供できるため、GetProducts() は指定されたサプライヤーの製品を返します。 これを実現するには、App_Code フォルダーで SuppliersRow.cs という名前の新しいクラス ファイルを作成し、次のコードを追加します。
using System;
using System.Data;
using NorthwindTableAdapters;
public partial class Northwind
{
public partial class SuppliersRow
{
public Northwind.ProductsDataTable GetProducts()
{
ProductsTableAdapter productsAdapter =
new ProductsTableAdapter();
return
productsAdapter.GetProductsBySupplierID(this.SupplierID);
}
}
}
この部分クラスは、Northwind.SuppliersRow クラスをビルドするときに、先ほど定義した GetProducts() メソッドも含めることをコンパイラに指示します。 プロジェクトをビルドしてからクラス ビューに戻ると、GetProducts() が Northwind.SuppliersRow のメソッドとして一覧表示されます。
図 34: GetProducts() メソッドが Northwind.SuppliersRow クラスに含まれるようになりました
次のコードが示すように、GetProducts() メソッドを使用して、特定のサプライヤーの製品セットを列挙できるようになりました。
NorthwindTableAdapters.SuppliersTableAdapter suppliersAdapter =
new NorthwindTableAdapters.SuppliersTableAdapter();
// Get all of the suppliers
Northwind.SuppliersDataTable suppliers =
suppliersAdapter.GetSuppliers();
// Enumerate the suppliers
foreach (Northwind.SuppliersRow supplier in suppliers)
{
Response.Write("Supplier: " + supplier.CompanyName);
Response.Write("<ul>");
// List the products for this supplier
Northwind.ProductsDataTable products = supplier.GetProducts();
foreach (Northwind.ProductsRow product in products)
Response.Write("<li>" + product.ProductName + "</li>");
Response.Write("</ul><p> </p>");
}
このデータは、任意の ASP.NET のデータ Web コントロールで表示することもできます。 次のページでは、2 つのフィールドを持つ GridView コントロールを使用します。
- 各サプライヤーの名前を表示する BoundField と
- 各サプライヤーの GetProducts() メソッドによって返される結果にバインドされる BulletedList コントロールを含む TemplateField。
このようなマスター/詳細レポートを表示する方法については、今後のチュートリアルで説明します。 ここでは、この例は Northwind.SuppliersRow クラスに追加されたカスタム メソッドの使用を説明することを目的としています。
SuppliersAndProducts.aspx
<%@ Page Language="C#" CodeFile="SuppliersAndProducts.aspx.cs"
AutoEventWireup="true" Inherits="SuppliersAndProducts" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
<link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<form id="form1" runat="server">
<div>
<h2>
Suppliers and Their Products</h2>
<p>
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False"
CssClass="DataWebControlStyle">
<HeaderStyle CssClass="HeaderStyle" />
<AlternatingRowStyle CssClass="AlternatingRowStyle" />
<Columns>
<asp:BoundField DataField="CompanyName"
HeaderText="Supplier" />
<asp:TemplateField HeaderText="Products">
<ItemTemplate>
<asp:BulletedList ID="BulletedList1"
runat="server" DataSource="<%# ((Northwind.SuppliersRow) ((System.Data.DataRowView) Container.DataItem).Row).GetProducts() %>"
DataTextField="ProductName">
</asp:BulletedList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</p>
</div>
</form>
</body>
</html>
SuppliersAndProducts.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class SuppliersAndProducts : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
SuppliersTableAdapter suppliersAdapter = new
SuppliersTableAdapter();
GridView1.DataSource = suppliersAdapter.GetSuppliers();
GridView1.DataBind();
}
}
図 35: サプライヤーの会社名が左側の列に、製品が右側の列に一覧表示されます (クリックするとフルサイズの画像を表示されます)
まとめ
Web アプリケーションを構築する際の最初の手順の 1 つに、プレゼンテーション層の作成を開始する前に行う DAL の作成があります。 Visual Studio を使用すると、型指定されたデータセットに基づく DAL を作成するタスクを、コードを 1 行も記述することなく 10 - 15 分で実行できます。 以降のチュートリアルは、この DAL に基づいています。 次のチュートリアルでは、いくつかのビジネス ルールを定義し、それらを個別のビジネス ロジック層で実装する方法について説明します。
プログラミングに満足!
もっと読む
この記事で説明したトピックの詳細については、次のリソースを参照してください。
- VS 2005 と ASP.NET 2.0 での厳密に型指定された TableAdapter と DataTable を使用した DAL の作成
- データ層コンポーネントの設計と層を介したデータの受け渡し
- ASP.NET 2.0 アプリケーションでの構成情報の暗号化
- TableAdapter の概要
- 型指定されたデータセットの操作
- Visual Studio 2005 と ASP.NET 2.0 での厳密に型指定されたデータ アクセスの使用
- TableAdapter メソッドを拡張する方法
このチュートリアルに含まれるトピックに関するビデオ トレーニング
著者について
7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジに取り組んでいます。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。
特別な感謝
このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は、Ron Green、Hilton Giesenow、Dennis Patterson、Liz Shulok、Abel Gomez、Carlos Santos でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、 にmitchell@4GuysFromRolla.com行をドロップしてください。