データソースとしてDataReaderを使用する
これまではレポートデザイナの「レポートデータソース」ダイアログを利用して帳票に表示するためのデータソースを指定してきましたが、ActiveReportsでは、DataReaderを利用して実行時にデータを取得しながら動的に帳票を生成していくこともできます。ここでは、DataReaderを使用してSQL Serverデータベースに格納されている志望校データにアクセスし、「第1志望校」「第2志望校」に表示する値を取得する手順について説明します。
DataReaderを使って1レコードづつデータを読み込む場合は、ActiveReports帳票にアンバウンドフィールドを設定し、実行時に読み込んだ値を動的にセットする必要があります。細かい点でいくつか違いはあるものの、大まかには前々回に紹介したようなテキストファイルの読み込みと似たような方法になるといえるでしょう。DataInitializeイベントでアンバウンドフィールドを作成し、FetchDataイベントでデータを1件ずつ取得していきます。
DataReaderを使って帳票を生成していく手順を大雑把にまとめると、次のようになります。
- ReportStartイベントではデータベースに接続し、DataReaderを生成する。
- DataInitializeイベントではデータ格納用のアンバウンドフィールドを作成する。
- FetchDataイベントでは1件分のデータを取得し、アンバウンドフィールドへ格納する。
- ReportEndイベントではDataReaderとデータベース接続をクローズする。
ここからは、各イベントごとに処理のコードを見ながら説明していきます。
ReportStartイベントの実装
ReportStartイベントでは、設定ファイル(App.config)から読み込んだ接続文字列をもとにデータベースとの接続を確立し、SQLを発行してDataReaderのインスタンスを取得します。
//SqlConnection, SqlDataReader, Dictionaryは //他のメソッドから参照するのでクラスメンバとして定義 private SqlConnection conn; private SqlDataReader dataReader; private Dictionary<string, string> subjects = new Dictionary<string, string>(); private void NewActiveReport_ReportStart(object sender, EventArgs e) { //科目コードを名称に変換するためのDictionaryを作る subjects["EMJ"] = "英・数・国"; subjects["EM"] = "英・数"; //設定ファイル(App.config)から接続文字列を取得し、 //データベースに接続する conn= new SqlConnection(); ConnectionStringSettings setting = ConfigurationManager.ConnectionStrings["sqlserver"]; conn.ConnectionString = setting.ConnectionString; conn.Open(); //SqlCommandを生成して実行し、DataReaderを取得する string query = "SELECT C.STUDENT_ID, S1.SCHOOL_NAME AS CHOICE1, " + "S2.SCHOOL_NAME AS CHOICE2 " + "FROM CHOICE C " + "LEFT OUTER JOIN SCHOOL AS S1 ON S1.SCHOOL_CODE = C.CHOICE1 " + "LEFT OUTER JOIN SCHOOL AS S2 ON S2.SCHOOL_CODE = C.CHOICE2 " + "ORDER BY C.STUDENT_ID "; SqlCommand cmd = new SqlCommand(query, conn); dataReader = cmd.ExecuteReader(); }
'SqlConnection, SqlDataReader, Dictionaryは '他のメソッドから参照するのでクラスメンバとして定義 Private conn As SqlConnection Private dataReader As SqlDataReader Private subjects As New Dictionary(Of String, String) Private Sub NewActiveReport1_ReportStart(_ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.ReportStart '科目コードを名称に変換するためのDictionaryを作る subjects("EMJ") = "英・数・国" subjects("EM") = "英・数" '設定ファイル(App.config)から接続文字列を取得し、 'データベースに接続する conn = New SqlConnection Dim setting As ConnectionStringSettings setting = ConfigurationManager.ConnectionStrings("sqlserver") conn.ConnectionString = setting.ConnectionString conn.Open() 'SqlCommandを生成して実行し、DataReaderを取得する Dim query _ = "SELECT C.STUDENT_ID, S1.SCHOOL_NAME AS CHOICE1, " _ & "S2.SCHOOL_NAME AS CHOICE2 " _ & "FROM CHOICE C " _ & "LEFT OUTER JOIN SCHOOL AS S1 ON S1.SCHOOL_CODE = C.CHOICE1 " _ & "LEFT OUTER JOIN SCHOOL AS S2 ON S2.SCHOOL_CODE = C.CHOICE2 " _ & "ORDER BY C.STUDENT_ID " Dim cmd As New SqlCommand(query, conn) dataReader = cmd.ExecuteReader() End Sub
DataInitializeイベントの処理
DataInitializeイベントでは、「第1志望校」「第2志望校」を格納するためのアンバウンドフィールドを生成します。ここではフィールド名を「SchoolOfChoice1」「SchoolOfChoice2」とします。
private void NewActiveReport1_DataInitialize(object sender, EventArgs e) { this.Fields.Add("SchoolOfChoice1"); this.Fields.Add("SchoolOfChoice2"); }
Private Sub NewActiveReport1_DataInitialize(_ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.DataInitialize Me.Fields.Add("SchoolOfChoice1") Me.Fields.Add("SchoolOfChoice2") End Sub
コードの記述が終わったら、志望校を表示するTextBoxコントロールのDataFieldプロパティに、先ほど定義した「SchoolOfChoice1」「SchoolOfChoice2」を設定します。
FetchDataイベントの処理
FetchDataイベントではDataReaderのReadメソッドを実行して、1件分のデータを取得します。データを取得できた場合は、DataInitializeイベントで生成したアンバウンドフィールドに値を設定します。次のデータがなくなったタイミングでFetchEventArgsのEOFプロパティをtrueとし、FetchDataのループ処理を終わります。
private void NewActiveReport1_FetchData(object sender, FetchEventArgs eArgs) { if (dataReader.Read()) { Fields["SchoolOfChoice1"].Value = dataReader["CHOICE1"].ToString(); Fields["SchoolOfChoice2"].Value = dataReader["CHOICE2"].ToString(); eArgs.EOF = false; //まだ次のデータがある } else { eArgs.EOF = true; //もう次のデータはない } }
Private Sub NewActiveReport1_FetchData( _ ByVal sender As System.Object, _ ByVal eArgs As _ DataDynamics.ActiveReports. ActiveReport3.FetchEventArgs) _ Handles MyBase.FetchData If datareader.Read() Then Me.Fields("SchoolOfChoice1").Value = _ dataReader("CHOICE1").ToString() Me.Fields("SchoolOfChoice2").Value = _ dataReader("CHOICE2").ToString() eArgs.EOF = False 'まだ次のデータがある Else eArgs.EOF = True 'もう次のデータはない End If End Sub
ReportEndイベントの処理
最後に、ReportEndイベントでは、SqlDataReaderとSqlConnectionをクローズします。
private void NewActiveReport1_ReportEnd(object sender, EventArgs e) { if (dataReader.IsClosed) { dataReader.Close(); } if (conn.State != ConnectionState.Closed) { conn.Close(); } }
Private Sub NewActiveReport1_ReportEnd(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.ReportEnd If datareader.IsClosed Then datareader.Close() End If If conn.State <> ConnectionState.Closed Then conn.Close() End If End Sub
DataReader使用時の注意点
DataReaderを使うと、最初にデータを1件取得した時点から帳票生成を開始できるため、高速な帳票生成を実現することができます。
しかし、帳票の生成処理が途中で異常終了するとすべてのデータが揃わず、不完全な帳票が出力されてしまうというデメリットもあります。実際の帳票アプリケーション開発では、別処理で表示するデータの件数をあらかじめ取得しておき、作成された帳票が途中で終了していないかチェックするなどの工夫が必要になります。
また、データアクセスのコードがActiveReportsの複数のイベントにまたがって実行されるため、エラー処理が複雑になりがちです。万が一エラーが発生してもデータベース接続やDataReaderのクローズ、リソースの解放がが確実に実行されるようにしてください。
おわりに
今回はXMLファイルやDataReaderをデータソースとして使用する方法と、ActiveReportsでのグラフ表示について紹介しました。
次回は、今回扱えなかったネットワーク帳票の話題や、ActiveReports帳票の構造の組み方とレスポンスの違いについて紹介する予定です。