SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

Visual Studio .NETのウィザードを使ってN階層アプリケーションを作成する:パート3

Visual Studio 2005でビジネスロジック層を効率扱う方法

ビジネス層の追加

 ビジネスロジックでパーシャルクラスを使用するという手法は、データを直接処理する小規模なコードに適しています。企業開発者の多くは、各エンティティのロジックをそれぞれ個別のクラスとして実装することを好みます。私は現在、ビジネスオブジェクトを多用する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文を使ってもかまいませんが、セキュリティを要する環境では、ストアドプロシージャを使ってデータにアクセスするのがよいでしょう。ウィザードで生成されるものでも十分ですが、データベース管理者が作成したストアドプロシージャも使用できます。既存のストアドプロシージャを使用する場合は、この次の画面で、データベース内のすべてのストアドプロシージャのリストから選択できます。

図2 テーブルアダプタコマンドの種類
図2 テーブルアダプタコマンドの種類

 次の画面では、クエリの使用目的を選択します。具体的には、行セットの選択、単一の値を返す、更新、削除、挿入の中から選択します。ほとんどの場合、クエリは行セットを返しますが、クエリによって更新や削除の方法を変更することもできます。

図3 テーブルアダプタクエリの種類
図3 テーブルアダプタクエリの種類

 各クエリはSQL文で定義します。この画面でSQL文を手入力することも、標準のクエリビルダを使ってビジュアルに作成することもできます。

図4 テーブルアダプタのSelect文
図4 テーブルアダプタのSelect文

 この時点で、特定の顧客のオーダーだけを取得するフィルタパラメータをクエリに追加できます。

図5 クエリビルダ
図5 クエリビルダ
図6 テーブルアダプタのメソッド名
図6 テーブルアダプタのメソッド名

 ウィザードの次の画面では、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メソッドを追加できるからです。

図7 リレーションに基づいてOrder Detailsにデータを読み込むクエリ
図7 リレーションに基づいてOrder Detailsにデータを読み込むクエリ

 あるいは、「Orders」テーブル内のレコードをループ処理し、複数のOrderIDを区切り文字で連結した文字列を作成して、それをIN()句フィルタで使うパラメータとして渡すという方法もあります。しかし、IN()句は変数を受け取らないので、OrderIDのリストをSQL文と直接組み合わせ、そのSQL文をEXEC関数で実行する必要があります。

 最も簡単なソリューションは、もう1つ別のクエリを「Order Details」テーブルアダプタに追加することです。このクエリでは、関係する2つのテーブルを結合し、図7のクエリビルダに示されているCompanyIDでフィルタするという処理を行います。このようなクエリは、「Order Details」テーブルを定義するプライマリクエリとしては正しく動作しません。なぜなら、更新時に更新対象のテーブルを見つけることが難しいからです。しかし、セカンダリクエリであれば適切に動作します。

複数のテーブルの更新

 前の記事では、データセットを更新してデータベースに戻す非常に単純な手順を紹介しました。しかし、関連テーブルを更新する場合、更新メソッドには特別な注意が必要です。テーブルを間違った順序で更新すると、子レコードの前に親レコードを削除しようとしたり、親レコードを挿入する前に子レコードを挿入しようとしたりします。

 関連テーブルを更新する場合の正しい順序は、1)削除される子レコードを送信する、2)親テーブルを更新する、3)子レコードを更新および追加する、というものです。この処理を実現するにはデータテーブルのGetChangesメソッドを使用します。今回の例では、まず新しい一時データテーブルを作成し、そのテーブルを、レコードの追加先または削除元となる現在のデータテーブルのサブセットとして定義します。その後、それぞれの一時データテーブルを正しい順序で更新します。

注意
 この形式のコードは、VBコードスニペットに含まれています。
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のリストに表示されるようになります。

図8 カスタマイズされたメソッドがIntellisenseに表示される
図8 カスタマイズされたメソッドが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」として参照できます。

次のページ
マクロを使ってデータアクセスコードを生成する

この記事は参考になりましたか?

japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

David Catherman(David Catherman)

CMI Solutions所属。データベースアプリケーションのデザイン/開発に20年以上の経験を持ち、ここ4、5年は特にMicrosoft .NETとSQL Serverに仕事が集中している。現在はCMI Solutionsのアプリケーション設計者および上級開発者であり、Visual Studioと...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/348 2006/04/24 16:33

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー