データモデルを設計しよう
前回は、ActiveReportsを利用した帳票アプリケーションで設計すべきポイントとして、「データモデルの定義」と「帳票レイアウトの定義」の2つがあると説明し、後者の「帳票レイアウトの定義」を中心に解説しました。今回は残っている「データモデルの定義」について解説していきますが、その前に、前回の内容を簡単におさらいしておきましょう。
帳票レイアウトの定義は「出力構造の定義」と「出力項目の定義」の2工程に分かれます。出力構造の定義では、ユーザーとの打ち合わせから得られたレイアウト案を元に出力構造を洗い出します。この工程では、帳票のレイアウトから繰り返し構造を抽出し、ActiveReportsのレポートファイルに用意された各種ヘッダ/フッタセクションとDetailセクションに対応づけていきます。
出力構造の定義が終わったら、次は出力項目の定義です。セクション定義済みのレポートファイルにLabelコントロールやTextBoxコントロールを配置して、出力項目の書式やフォント、折り返し設定などを定めていきます。出力項目を表すコントロールのDataFieldプロパティにレポートデータソースの各フィールド名を設定することで、ActiveReportsの帳票アプリケーションは完成します。
データモデルを意識しなくても帳票は作れる?
帳票アプリケーション開発の現場で使われている設計書を見てみると、出力項目の配置や書式など「帳票レイアウトの定義」は割と丁寧に記述されていることが多いのですが、帳票アプリケーションが読み取るためのデータについてはあまり細かく説明されていないことがあります。直接目にする帳票レイアウトと比べて、データモデルのほうは意識されることが少ない、ということでしょうか。
ActiveReportsでは、レポートデザイナのDetailセクションに表示されたアイコンをクリックして「レポートデータソース」ダイアログを起動し、データベースの接続やXMLファイルの情報を与えるだけで、(データの形をあまり考えなくても)簡単にデータバインドを実装できます。
これは間違いなくActiveReportsの大きな長所なのですが、反面、いきあたりばったりの実装でもそこそこ動くアプリケーションができてしまい、複雑なSQLを駆使した帳票の集計ミスが後になって見つかることも少なくありません。
帳票アプリケーションを設計するときは、目に見える帳票レイアウトの定義だけでなく、どのような構造のデータを読み取るのかについてもしっかり定義しておくことが重要です。今回は、ActiveReportsのレポートファイルで使うためのデータ構造を設計するポイントについて解説していきたいと思います。
データモデル設計のメリット
前回、ActiveReportsのレポートファイルで使うためのデータ構造を「データモデル」と呼ぶことにしました。帳票アプリケーションを開発するときは、データモデルを明確に定義しておくことで場当たり的な実装をけん制し、品質の高いアプリケーションを開発できるようになります。
ActiveReportsでは、レポートデータソースとしてSQL ServerなどのRDBMSや、CSVやXMLといったデータファイルなどさまざまな形式のデータに接続して帳票を出力できます。しかし、どのようなデータソースであってもDetailセクションに定義された1件分のデータを繰り返し出力していくのが基本です。そのため、ActiveReportsを利用した帳票アプリケーションの設計では、RDBのテーブルと同じような「行×列の表形式で表されるデータ構造」をベースにデータモデルを考えていくのがよいでしょう。
読者の皆さんの中には「このようなものを定義してなんの意味があるのか」と思われる方もいるかもしれません。しかし、帳票アプリケーション設計の段階でデータソースに依存しない形でデータモデルを定義しておくと、データの論理構造と表現形式の分離がはっきりとし、メンテナンス性が向上するというメリットがあります。例えば、それまでXMLファイルを読み込んで出力していた帳票アプリケーションがあったとしましょう。このXMLファイルは帳票アプリケーションのデータモデルに準拠した構造になっています。ここで、同じようにデータモデルに準拠した構造でRDBのテーブルを作れば、XMLベースのファイル帳票アプリケーションがそのまま、データベース帳票アプリケーションとして再利用できるのです。
この他にも、独立したデータモデルを作っておくと「開発用データベースが準備できるまではテスト用のスタブで仕事を進めておく」といった手段をとることもでき、開発効率の面でもメリットがあります。
また、レポートデータソースとして指定するSELECT文やXMLファイルを直接読むよりも、データソースとは独立したデータモデルを用意しておくほうが、その帳票が何を出力するのかがわかりやすくなります。データモデルはあくまで抽象的な型の定義なので、ソースファイルとして実装するには具象クラスを使うよりもインタ-フェイスを使うのがよいでしょう。たとえばDetailセクション1件分を表す型をIDetailというインタ-フェイスで定義しておき、データモデル全体はIEnumerable<IDetail>のような形にしておけば、データソースの種類に依存しないモデルを作ることができます。また、IEnumerableインタ-フェイスにしておくことで、LINQによる操作も容易になります。
以下は、先ほど紹介した表形式のデータモデルをC#/VBのインターフェイスとして定義したものです。
public interface IDetail { public int Year { get; set; } public int Quarter { get; set; } public int Month { get; set; } public decimal SalesAmount { get; set; } }
Public Interface IDetail Property Year() As Integer Property Quarter() As Integer Property Month() As Integer Property SalesAmount() As Decimal End Interface
データモデルを用意しておくと、以下のように帳票生成部分のロジックをデータソースと分離することができます。帳票生成部分であるForm_Loadメソッドの中身を変えることなくGenerateDataメソッドを変更するだけで柔軟にデータソースを取り替えることが可能です。
private IEnumerable<IDatail> GenerateData() { //データ生成処理を記述 } private void Form1_Load(object sender, EventArgs e) { IEnumerable<IDetail> data = GenerateData(); NewActiveReport1 rpt = new NewActiveReport1(); rpt.DataSource = data.ToList(); rpt.Run(); }
Private Function GenerateData() As IEnumerable(Of IDetail) 'データ生成処理を記述 End Function Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Dim data = GenerateData() Dim rpt As New NewActiveReport1 rpt.DataSource = data.ToList() rpt.Run() End Sub