ビジネス層の追加
ビジネスロジックでパーシャルクラスを使用するという手法は、データを直接処理する小規模なコードに適しています。企業開発者の多くは、各エンティティのロジックをそれぞれ個別のクラスとして実装することを好みます。私は現在、ビジネスオブジェクトを多用するKevin McNeishのMere Mortals Frameworkを使って作業をしています。このフレームワークでは、型指定されたデータセットを本稿の例と同じくらい簡単に使用でき、独立したビジネスオブジェクトクラスをインスタンス化して、既存のデータセット内のデータテーブルを参照します。この方法だと、すべてのビジネスロジックを含むビジネスオブジェクトクラスを作成できるので再利用性が高まる上に、データソースウィザードを使って画面を簡単に作成することができます。
ビジネスロジック
本シリーズのパート2で説明したように、ビジネスロジックの重要なポイントの1つは、いかにしてデータアクセス層を呼び出すかです。まず、個々のテーブルアダプタをインスタンス化し、データセット内のデータテーブルにデータを読み込むメソッドを用意する必要があります。これらのテーブルは更新可能なので、更新内容をデータベースに渡すメソッドも用意しなければなりません。パート2の例に示した5つのテーブルから成るNorthwindデータセットの場合、必要なデータアクセスコードは次のようになります。
Imports ta = NorthwindDataSetTableAdapters Partial Public Class NorthwindDataSet Private taOrders As New ta.OrdersTableAdapter Private taOrderDetail As New ta.Order_DetailsTableAdapter Private taCustomer As New ta.CustomersTableAdapter Private taEmployee As New ta.EmployeesTableAdapter Private taProduct As New ta.ProductsTableAdapter Public Sub FillDataSetAll() Me.taOrders.Fill(Me.Orders) Me.taOrderDetail.Fill(Me.Order_Details) Me.taCustomer.Fill(Me.Customers) Me.taEmployee.Fill(Me.Employees) Me.taProduct.Fill(Me.Products) End Sub Public Sub UpdateOrders() Me.taOrders.Update(Me.Orders) End Sub Public Sub UpdateOrderDetails() Me.taOrderDetails.Update(Me.Order_Details) End Sub End Class
これはごく簡単な例であり、データベース内の全レコードをデータテーブルに読み込んでいます。大きなデータベースの場合は、読み込むレコードをフィルタパラメータで制限します。
パラメータ付きクエリ
これまでご紹介しませんでしたが、データセットデザイナにはパラメータ付きクエリを追加できる機能があります。この機能を使うと、テーブル内のすべてのレコードが必要でないときに、目的のデータだけを取得できます。データセットを開き、いずれかのテーブルアダプタのヘッダーを右クリックすると、使用できる機能の一覧が表示されます。[Add Query]オプションをクリックするとテーブルアダプタ構成ウィザードが表示され、SQL文の使用、ストアドプロシージャの新規作成、既存のストアドプロシージャの使用というオプションが表示されます。単純なクエリの場合はSQL文を使ってもかまいませんが、セキュリティを要する環境では、ストアドプロシージャを使ってデータにアクセスするのがよいでしょう。ウィザードで生成されるものでも十分ですが、データベース管理者が作成したストアドプロシージャも使用できます。既存のストアドプロシージャを使用する場合は、この次の画面で、データベース内のすべてのストアドプロシージャのリストから選択できます。

次の画面では、クエリの使用目的を選択します。具体的には、行セットの選択、単一の値を返す、更新、削除、挿入の中から選択します。ほとんどの場合、クエリは行セットを返しますが、クエリによって更新や削除の方法を変更することもできます。
各クエリはSQL文で定義します。この画面でSQL文を手入力することも、標準のクエリビルダを使ってビジュアルに作成することもできます。
この時点で、特定の顧客のオーダーだけを取得するフィルタパラメータをクエリに追加できます。
ウィザードの次の画面では、FillByタイプのメソッド(既存のデータテーブルを使用する場合)とGetDataByタイプのメソッド(データテーブルオブジェクトを返す)の名前を編集できます。[Finish]ボタンをクリックすると、クエリが完成します。
データセットデザイナにより、テーブル表現のテーブルアダプタセクションに新しいメソッドの行が追加されます。CustomerIDパラメータを渡してFillByCustomerIDメソッドを呼び出すと、オーダーの一部だけをデータテーブルに読み込むことができます。
オプションパラメータの使用
テーブルアダプタに柔軟性を与えるもう1つの方法は、オプションパラメータを使用し、ユーザーが1回の呼び出しで複数のパラメータを選択できるようにすることです。今回のサンプルでは、次に示すように、WHERE節にOR @Parameter IS NULLを追加することによって、1つのクエリでCustomerIDまたはEmployeeIDを使って読み込みレコードを制限できるようにしています。
SELECT * FROM Orders WHERE (CustomerID = @CustomerID OR @CustomerID IS NULL) AND (EmployeeID = @EmployeeID OR @EmployeeID IS NULL) AND NOT (@CustomerID IS NULL AND @EmployeeID IS NULL)
この例では、いずれかのパラメータまたは両方のパラメータを渡すことができますが、何も渡さなかった場合は、空の結果セットが得られます。
型指定されたデータセットは、オプションパラメータを適切に処理します。ジェネレータは値型に対してNullable(型)パラメータを作成し、それをdbnullに正しく変換します。
型指定されたデータセットにおけるオプションパラメータの唯一の欠点は、自分自身でパラメータコレクションに対処しなければならないことです。ほとんどの場合、AllowDBNullプロパティを手動で設定する必要があり、クエリを再構成するたびにウィザードがその状態をリセットします。
リレーションを使ってテーブルにデータを読み込む
「Orders」テーブル内のレコード数を制限したため、「Order Details」テーブルにデータを読み込むときにも行数を制限しなければなりません。リレーションシップによる読み込み機能が追加されていればよいのですが、このバージョンには用意されていません。従って、自分で対処する必要があります。
ストアドプロシージャを使っている場合は、SQL Server 2005のMARS(Multiple Active Result Sets)機能を使って、一度に両方のテーブルにデータを返すことができます。結果セットを正しいデータテーブルにマップする特別なコードを追加する必要がありますが、それは大して難しくありません。このコードを記述するにはテーブルアダプタのパーシャルクラスを使用するとよいでしょう。テーブルアダプタのパーシャルクラスでは、データアダプタオブジェクトにアクセスしてTableMappingsメソッドを追加できるからです。
あるいは、「Orders」テーブル内のレコードをループ処理し、複数のOrderIDを区切り文字で連結した文字列を作成して、それをIN()句フィルタで使うパラメータとして渡すという方法もあります。しかし、IN()句は変数を受け取らないので、OrderIDのリストをSQL文と直接組み合わせ、そのSQL文をEXEC関数で実行する必要があります。
最も簡単なソリューションは、もう1つ別のクエリを「Order Details」テーブルアダプタに追加することです。このクエリでは、関係する2つのテーブルを結合し、図7のクエリビルダに示されているCompanyIDでフィルタするという処理を行います。このようなクエリは、「Order Details」テーブルを定義するプライマリクエリとしては正しく動作しません。なぜなら、更新時に更新対象のテーブルを見つけることが難しいからです。しかし、セカンダリクエリであれば適切に動作します。
複数のテーブルの更新
前の記事では、データセットを更新してデータベースに戻す非常に単純な手順を紹介しました。しかし、関連テーブルを更新する場合、更新メソッドには特別な注意が必要です。テーブルを間違った順序で更新すると、子レコードの前に親レコードを削除しようとしたり、親レコードを挿入する前に子レコードを挿入しようとしたりします。
関連テーブルを更新する場合の正しい順序は、1)削除される子レコードを送信する、2)親テーブルを更新する、3)子レコードを更新および追加する、というものです。この処理を実現するにはデータテーブルのGetChangesメソッドを使用します。今回の例では、まず新しい一時データテーブルを作成し、そのテーブルを、レコードの追加先または削除元となる現在のデータテーブルのサブセットとして定義します。その後、それぞれの一時データテーブルを正しい順序で更新します。
Public Sub UpdateDB() Dim DeletedChildRecords As DataTable = _ Me.Order_Details.GetChanges(DataRowState.Deleted) Dim NewChildRecords As DataTable = _ Me.Order_Details.GetChanges(DataRowState.Added) Dim ModifiedChildRecords As DataTable = _ Me.Order_Details.GetChanges(DataRowState.Modified) Try If Not DeletedChildRecords Is Nothing Then taOrderDetail.Update(DeletedChildRecords) DeletedChildRecords.Dispose() End If taOrders.Update(Me.Orders) If Not ModifiedChildRecords Is Nothing Then taOrderDetail.Update(ModifiedChildRecords) ModifiedChildRecords.Dispose() End If If Not NewChildRecords Is Nothing Then taOrderDetail.Update(NewChildRecords) NewChildRecords.Dispose() End If Me.AcceptChanges() Catch ex As Exception Throw ex End Try End Sub
他のビジネスロジックの追加
データセットのパーシャルクラスは、実際のデータアイテムを処理するビジネスロジックを実装するのに最適な場所です。今回の例では、テーブルアダプタをインスタンス化するのにデータセットのパーシャルクラスを使用しましたが、各テーブル(行コレクション)、テーブルの個々の行、行変更イベントについてもサブパーシャルクラスがあります。データセットのパーシャルクラスの外部では、各テーブルアダプタのパーシャルクラスにもアクセスできます。
例えば、同じ顧客の2つのオーダーを統合するために、あるOrderのすべてのOrder Detailレコードを別のOrderに移動するメソッドを作成するとします。この場合は、データセットのパーシャルクラス内に次のクラス(サブクラス)を作成できます。
Partial Public Class OrdersDataSet ... Partial Public Class Order_DetailsDataTable Public Sub MoveDetailRecords(ByVal FromOrderID As Integer, ByVal ToOrderID As Integer) For Each row As Order_DetailsRow In Me.Select( _ "OrderID=" & FromOrderID) row.OrderID = ToOrderID Next End Sub End Class ... End Class
フォームコードに戻ると、新しいメソッドがIntellisenseのリストに表示されるようになります。
もう1つの例として、顧客の住所情報をオーダーの出荷先セクションにコピーするメソッドを考えてみましょう。
Partial Public Class OrdersRow Public Sub ShipToCustomer() If Me.CustomerID Is Nothing Then 'TODO: should throw an exception MsgBox("Customer not defined") Return End If Dim CustTable As CustomersDataTable = _ Me.Table.DataSet.Tables("Customers") Dim CustRow As CustomersRow = _ CustTable.FindByCustomerID(Me.CustomerID) Me.ShipName = CustRow.ContactName Me.ShipAddress = CustRow.Address Me.ShipCity = CustRow.City Me.ShipRegion = CustRow.Region Me.ShipPostalCode = CustRow.PostalCode Me.ShipCountry = CustRow.Country End Sub End Class
フォームコード内でOrderRowオブジェクトへの参照を取得すると、このShipToCustomerメソッドを使って出荷先住所をコピーすることができます。
ここで紹介したのは、データセットのパーシャルクラス内でビジネスロジックを作成する方法の2つの例に過ぎません。この方法の利点は、すべてのデータセットオブジェクトをコンテキスト内で使用できることです。別のクラスをビジネスオブジェクトとして作成する場合は、データオブジェクトにアクセスするためにデータセットへの参照を渡す必要があります。上記の例は、データセットオブジェクト内のコードなので、データオブジェクトを「Me」として参照できます。







