CodeZine(コードジン)

特集ページ一覧

【DioDocs帳票生成ハンズオン】強力な新機能「テンプレート構文」を使ってみる

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

目次

データソース

 では実際にテンプレート構文を利用していきましょう。そのためにはまず、「データソース」について理解する必要があります。

 DioDocsのテンプレート構文ではWPFなどのデータバインディングのような機構が利用できます。その際にデータソースとしては次の3種類が利用できます。

  • 変数
  • カスタムオブジェクト
  • データテーブル・データセット

変数

 まずは「変数」から利用していきましょう。先ほどのコードを修正します。

 直接セルに値を設定していた箇所をコメントアウトし、以下の通りデータソースを設定するように修正してください。

//workbook.Worksheets[0].Range["B5"].Value = "Hello, DioDocs!";
workbook.AddDataSource("CompanyName", "Hello, DioDocs!");
workbook.ProcessTemplate();

 AddDataSourceの後に、ProcessTemplateメソッドを呼び出すことで、データソースの値をExcelファイルに適用します。

 続いて、Excelファイルを次のように修正しましょう。

AddDataSourceで指定した名称を、二重の波カッコで囲んで記述
AddDataSourceで指定した名称を、二重の波カッコで囲んで記述

 値を設定していた「B5」セルに、AddDataSourceで指定した名称を二重の波カッコで囲んで{{CompanyName}}のように記述します。

 では実行してみましょう。修正前と同じPDFが得られたはずです。

 このように物理的なレイアウト情報をExcel側で指定し、論理名を利用してマッピングすることで、ExcelのテンプレートファイルとC#のコードを疎結合に保てることがテンプレート構文の最大の利点になります。

カスタムオブジェクト

 さて、変数を1つずつ指定して複雑な帳票を生成することも可能ですが、実際には必要なプロパティを保持した、帳票を抽象化したオブジェクトを扱えると便利ですよね。それが「カスタムオブジェクト」です。

 今回は請求書を作成します。なので請求書クラスを作成して利用してみましょう。まずは次のようにInvoiceクラスを作成してください。

namespace HelloDioDocs
{
    public class Invoice
    {
        public string CompanyName { get; set; }
    }
}

 これを利用して帳票を生成します。先ほどの値を設定していたコードを、以下の通り修正します。

//workbook.AddDataSource("CompanyName", "Hello, DioDocs!");
var invoice = new Invoice {CompanyName = "Hello, DioDocs!"};
workbook.AddDataSource("Invoice", invoice);
workbook.ProcessTemplate();

 そしてExcelファイルも次のように修正します。

 {{CompanyName}}を{{Invoice.CompanyName}}に修正
 {{CompanyName}}を{{Invoice.CompanyName}}に修正

 {{CompanyName}}{{Invoice.CompanyName}}に修正します。データソースの名称とプロパティ名をピリオドでつないで記述します。

 では実行して、先と同様なPDFが生成されることを確認してください。

 このようにして複雑な構造のオブジェクトもテンプレート構文で扱うことが可能です。

データセット・データテーブル

 データソースの最後は「データセット」と「データテーブル」です。データセットはデータテーブルを複数束ねて利用するだけなので本稿では割愛し、データテーブルのみを扱います。

 データテーブルをプログラムから生成することも可能ですが、次の2つの理由からデータベースを利用してデータを取得します。

  • データテーブルを利用する場合、ほとんどのユースケースでデータベースから値を取得する
  • 後の大量データ生成のため

データベースのセットアップ

 データベースはMicrosoftの提供するサンプルデータベース「AdventureWorks」を利用します。

 データベースを利用するとは言え、Dockerさえ入っていれば何も煩わしいことはありません。いくつかのスクリプトを実行するだけです。ステップは次の通りです。

  1. コンテナイメージの取得
  2. コンテナの作成
  3. コンテナの起動
  4. 利用
  5. コンテナの停止
  6. コンテナの削除
  7. イメージの削除
1.コンテナイメージの取得

 まずはコマンドプロンプトを開き、次の通りコンテナイメージをDockerHubから取得してください。

docker pull nuitsjp/adventureworks:latest

 コンテナイメージとは、OSをインストールする際のisoファイルに置き換えて考えると理解しやすいでしょう。

2.コンテナの作成

 取得したイメージからadventureworksという名称のコンテナを作成します。ここでは、saパスワードとSQL Serverを接続するポートをあわせて指定してコンテナを作成しています。

docker create --name adventureworks -e ACCEPT_EULA=Y -e SA_PASSWORD=P@ssw0rd! -p 1433:1433 nuitsjp/adventureworks:latest

 isoイメージを実際のPC(もしくは仮想PC)にインストールする感覚です。

3.コンテナの起動

 続いてコンテナを起動します。

docker start adventureworks
4.利用

 これでAdventureWorksが利用できるようになりました。SSMSがインストールされているのであれば、実際に試してみてもいいでしょう。

 SSMSがインストールされていない場合は、特に何もしなくて構いません。すぐにコードから利用します。また、念のため停止や削除の方法も記載しておきましょう。

5.コンテナの停止

 明示的に停止する場合は、次のようにコマンドを実行してください。またPCをシャットダウンしても停止し、起動時に自動的に開始されることはありません。

docker stop adventureworks
6.コンテナの削除

 ローカルのリソースを削除するためにはコンテナとイメージの2つを削除する必要があります。まずはコンテナを削除しましょう。

docker rm adventureworks
7.イメージの削除

 最後にイメージを削除すれば、すべて元通りです。

docker rmi nuitsjp/adventureworks:latest

Hello, DataTable!

 では実際にコードからデータベースへ接続し、結果を利用してみましょう。

 まずはSQL Serverへ接続するためのライブラリ「Microsoft.Data.SqlClient」をNuGetからインストールします。手順はDioDocsのライブラリをインストールしたときと同じです。そちらを参照してください。

 .NET FrameworkではSQL Serverへ接続するためのライブラリは標準で含まれていました。しかし、.NET Coreではオプショナル的なパッケージは外部に切り出され、小さな単位で配備できるように分割されました。これは、たとえばスケーラブルなシステムを構築する際に、スタートアップ時間の削減などの効果が期待できるからです。

 続いてSQLを用意しましょう。SQLはコードに直接記述すると読みにくいため、今回はファイルに記述します。プロジェクトを右クリックし、「追加」から「新しい項目」を選択しましょう。

「追加」から「新しい項目」を選択
「追加」から「新しい項目」を選択

 検索条件に「テキスト」を入力し「テキスト ファイル」を選択してください。ファイル名に「SelectInvoices.sql」を指定して「追加」します。

ファイル名に「SelectInvoices.sql」を指定して「追加」
ファイル名に「SelectInvoices.sql」を指定して「追加」

 追加したら「Template.xlsx」と同様に、「出力ディレクトリにコピー」プロパティの値を「新しい場合はコピーする」を選択してください。

 続いてSQLファイルを開き、以下の通り記述してください。

use AdventureWorks;

select 
  Name as CompanyName 
from 
  Sales.Store 
where 
  BusinessEntityID = 1046

 AdventureWorksデータベースのSalesスキーマのStoreテーブルから店舗を取得します。

 ではこのSQLを実行してDioDocsへ渡すコードを記述しましょう。カスタムオブジェクトを設定していたコードをコメントアウトし、代わりに次のように記述しましょう。

//var invoice = new Invoice {CompanyName = "Hello, DioDocs!"};
//workbook.AddDataSource("Invoice", invoice);

var connectionStringBuilder =
    new SqlConnectionStringBuilder
    {
        DataSource = "localhost",
        UserID = "sa",
        Password = "P@ssw0rd!"
    };
using var connection = new SqlConnection(connectionStringBuilder.ToString());
connection.Open();

using var command =
    new SqlCommand(
        File.ReadAllText("SelectInvoices.sql"),
        connection);
using var dataTable = new DataTable();
dataTable.Load(command.ExecuteReader());

workbook.AddDataSource("Invoice", dataTable);

 順に説明していきましょう。

 まずは、次のようにして接続文字列を生成してデータベースへのコネクションを開いています。

var connectionStringBuilder =
    new SqlConnectionStringBuilder
    {
        DataSource = "localhost",
        UserID = "sa",
        Password = "P@ssw0rd!"
    };
using var connection = new SqlConnection(connectionStringBuilder.ToString());
connection.Open();

 続いてSQLをファイルから読み込んで実行し、実行結果をDataTableにロードしています。

using var command =
    new SqlCommand(
        File.ReadAllText("SelectInvoices.sql"),
        connection);
using var dataTable = new DataTable();
dataTable.Load(command.ExecuteReader());

 最後にDataTableをDioDocsのデータソースとして追加しています。

workbook.AddDataSource("Invoice", dataTable);

 では実行してみましょう。先ほどと名称が変わっていますが、次のような似通ったPDFが生成されます。

生成されたPDF
生成されたPDF

カスタムオブジェクトvsデータセット

 さて変数はさておき、実践的な帳票生成において機能的にカスタムオブジェクトとデータセット(もしくはデータテーブル)のいずれも選択できる場合、どれを利用するべきでしょうか?

 結論から言うと、高度なUIを含むアプリケーションの内部に包含するのであれば「カスタムオブジェクト」を、単一種類の帳票をシンプルなルールで出力するような専用のバッチプログラムの場合は「データセット」を選ぶというのが、私の好みに合います。

 もちろん前提として、実現できる帳票に差異がなく、性能に大幅に差がない場合に限ります。次の表は、簡単なベンチマークの実行結果です。

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.836 (1903/May2019Update/19H1)
Intel Core i7-7700T CPU 2.90GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.201
  [Host]     : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT  [AttachedDebugger]
  DefaultJob : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT
Method Mean Error StdDev
DataSet 12.66 ms 0.482 ms 1.384 ms
CustomObject 10.73 ms 0.461 ms 1.338 ms

 どちらも非常に高速に動作していますね。素晴らしい。CustomObjectのほうがやや高速ですが、それでも2ミリ秒の差異のため、実用上の差異は気にならないケースがほとんどかと思います。

 と言うわけで、先ほどの指針で問題ないように思えます。

 カスタムオブジェクトを利用する場合、データセットを利用する場合と比較して、最低でもカスタムオブジェクトの設計・実装というコストがかかります。しかし逆に言うと、帳票の元データのデータストアとビジネスルール、Excelテンプレートの3つの結合度を低く保つことが可能となります。

 データセットをダイレクトに利用する場合、データストアの具体的な実装に強く依存してしまいます。その分手軽で生産性は高くなりますが、高度なUIを含むアプリケーションの内部に埋め込んでしまうと、テスト容易性や変更容易性に大きな課題を残してしまいます。

 高度なUIを含むアプリケーションの内部に包含するのであればカスタムオブジェクトを、単一種類の帳票をシンプルなルールで出力するような専用のバッチプログラムの場合はデータセットを選ぶのが、第一の判断基準として良いように思います。もちろん例外は存在するでしょう。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

バックナンバー

連載:クラウド時代にマッチする、ドキュメント生成・更新APIライブラリ「DioDocs(ディオドック)」
All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5