マクロを使ってデータアクセスコードを生成する
データアクセス層のコードは、テーブル名が異なるだけで繰り返しが多いので、コードの自動生成に適しています。コードジェネレータにはさまざまな種類があり、それぞれに長所と短所があります。私は最近、マクロを使ってコードを生成する方法を調べています。この方法の長所は、IDEのコンテキストで生成が行われることです。すべての書式化、インデント、Endタグの挿入が、手動でコードを入力しているときと同じように行われます。
マクロを作成するには、VSの上部メニューから[Tools]→[Macros]→[Macros IDE]を選択します。VSに似た別のIDEが開き、マクロを編集できます。クラスリストの[MyMacros]を右クリックし、GenDataAccessLayerという名前の新しいクラス(モジュール)を追加します。新しいクラスの内側に、以降で紹介するメソッドを作成します。
このマクロでは、リフレクションを使ってデータセットを参照し、各テーブルアダプタをインスタンス化して、それぞれのFillメソッドを呼び出します。まず、次の名前空間をインクルードする必要があります。
Imports EnvDTE Imports EnvDTE80 Imports System.Diagnostics Imports System.Reflection Imports System
マクロ内では、Visual Studio統合開発環境(DTE)を通じて、アクティブ文書へのポインタを使用できます。このポインタは、ソリューションエクスプローラ内で現在開いているアイテムを指します。また、そのアイテムがどのプロジェクトに含まれているかも認識しています。
Public Sub DefineTAviaReflectionVB() Dim pItem As ProjectItem = DTE.ActiveDocument.ProjectItem Dim ItemName As String = _ pItem.Name.Substring(0, pItem.Name.LastIndexOf(".")) Dim proj As Project = pItem.ContainingProject Dim line As String = "" Dim TableName As String Dim taList As String Dim UpdateList As String = "" Dim UpdateType As MethodInfo
完全プロジェクト名に基づいて、実際のアセンブリのパスを作成します。BusinessクラスライブラリではなくWindows Formsアセンブリを作成する場合は、.DLL拡張子ではなく、.EXE拡張子を使用します。
ProjName = ProjName.Substring(0, ProjName.LastIndexOf("\") + 1) ProjName = ProjName & "bin\debug\" & proj.Name & ".dll"
アセンブリが既にある場合は、このパスからアセンブリをロードできます。プロジェクト自体からアセンブリへの参照を取得するもっとよい方法があるのかもしれませんが、今のところ私はこの方法しか知りません。
Dim targetAssembly As Assembly = Assembly.LoadFrom(ProjName)
アセンブリ内の個々の型をループ処理し、データセットテーブルアダプタのグループの中から末尾が「TableAdapter」のものを探します。
For Each typ As Type In targetAssembly.GetTypes() If typ.FullName Like "*" & ItemName & "TableAdapters." & _ "*TableAdapter" Then TableName = typ.Name.Substring(0, _ typ.Name.LastIndexOf("TableAdapter"))
実際にコードを生成する部分は次の通りです。
line = "Dim ta" & TableName & " As " & ItemName & _ "TableAdapters." & typ.Name DTE.ActiveDocument.Selection.NewLine() DTE.ActiveDocument.Selection.Text = line
後でFillメソッドを作成するために、オブジェクト名を保存しておきます。
taList &= "ta" & TableName & ","
テーブルアダプタにUupdateメソッドがあるかどうかをチェックし(実際には、Deleteメソッドをチェックする方が簡単です)、後で使うためにテーブルアダプタを保存します。
UpdateType = typ.GetMethod("Delete")
If UpdateType IsNot Nothing Then
UpdateList &= "ta" & TableName & ","
End If
Next
各テーブルアダプタに対してFillメソッドを呼び出すためのメソッドを作成します。
DTE.ActiveDocument.Selection.EndOfLine()
DTE.ActiveDocument.Selection.NewLine()
'Add the Update methods
For Each ta As String In UpdateList.Split(",")
If ta.Length > 0 Then
DTE.ActiveDocument.Selection.Text = "Public Sub Update" _
+ ta.Substring(2) + "()"
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.Text = ta & ".Update(" & _
ta.Substring(2) & ");"
DTE.ActiveDocument.Selection.NewLine()
End If
Next
targetAssembly = Nothing
End Sub
Northwindサンプルにこのコードを適用すると、次のコードが得られます。
Partial Class NorthwindDataSet Dim taCustomers As _ New NorthwindDataSetTableAdapters.CustomersTableAdapter Dim taEmployees As _ New NorthwindDataSetTableAdapters.EmployeesTableAdapter Dim taOrder_Details As _ New NorthwindDataSetTableAdapters.Order_DetailsTableAdapter Dim taOrders As _ New NorthwindDataSetTableAdapters.OrdersTableAdapter Dim taProducts As _ New NorthwindDataSetTableAdapters.ProductsTableAdapter Public Sub FillAll() taCustomers.Fill(Customers) taEmployees.Fill(Employees) taOrder_Details.Fill(Order_Details) taOrders.Fill(Orders) taProducts.Fill(Products) End Sub Public Sub UpdateOrder_Details() taOrder_Details.Update(Order_Details) End Sub Public Sub UpdateOrders() taOrders.Update(Orders) End Sub End Class
ここで紹介したロジックについてご意見などがありましたら、ぜひお寄せください。データセットが有効な間はテーブルアダプタをインスタンス化したままにしておく方がいいのか(このオブジェクトのメモリヒットが高くなります)、必要になるたびに(プロセッサがこのオブジェクトのインスタンス化を要求するたびに)インスタンス化して、メモリをできるだけ解放しておく方がいいのか、意見はいろいろあると思います。今回の例では、インスタンス化したままにしておいたので、Update呼び出しでもそのまま使用できます。
