ActiveReports通信

.NET帳票開発ツール ActiveReports の技術情報をお届けします。

Azure Cosmos DBから取得したデータで帳票を作成する

大規模データの解析などで注目を集める「NoSQL」。海外のActiveReportsのお客様からは、すでにNoSQLデータベースとの連携方法についていくつか問い合わせをいただいており、今後は日本国内でも需要の増加が予想されます。

今回はMicrosoft AzureのNoSQLデータベース「Azure Cosmos DB」とActiveReportsの連携方法の一例をご紹介します。

Azure Cosmos DBとは

Azure Cosmos DB は、Microsoft Azure が提供するNoSQL データベースのPaaSです。詳細は公式ブログの記事が参考になります。

Cosmos DB ことはじめ – Microsoft Japan Data Platform Tech Sales Team Blog

かつては同様のサービスとしてAzure DocumentDBがありましたが、2017年5月より名称がAzure Cosmos DBに変更され、DocumentDB以外にも「MongoDB API」、「Graph API」、「テーブル API」など複数のAPIをサポートしたマルチモデルの分散データベースになりました。今回はそのうちのDocumentDB APIとの連携方法を紹介します。

実装方法

Microsoft Azureのデータベースといえば、RDBMSの「Azure SQL Database」の方が有名かと思いますが、こちらにはActiveReportsが提供するネイティブの「Microsoft SQL Client Provider」を使用して接続できます。

一方、Azure Cosmos DBとの接続用のドライバーは現在提供しておりませんが、DocumentDBのデータ(ドキュメント)はJSON形式なので、JSONデータさえ取得してしまえば、あとは先日公開した記事でも紹介した「JSONデータソース」の機能を応用し、実行時にLocateDataSourceイベントを経由して、レポートにデータを供給できます。

全体の流れを簡単にまとめると以下のようになります。

  1. Azure Cosmos DBへの接続用クライアントのインスタンスを作成する
  2. クライアントのインスタンスからDBへクエリを実行し、JSONデータを取得する
  3. 取得したJSONデータを、ActiveReportsのJSONデータソースが読み込めるよう、1つのJSONデータに成型する
  4. LocateDataSourceイベントから、成型したJSONデータをレポートのデータソースのデータとして供給する

※LocateDataSourceイベントは、レポートの生成時に、使用するデータが見つからない場合に発生するイベントです。アンバウンドレポート(レコードを取得するためにデータソースを必要としないレポートのこと)において、実行時に外部のデータと接続する場合に使用します。

<参考情報>
実行時のレポートとデータソースの連結
JSONデータを使ったレポート

Azure Cosmos DBの作成

まずはAzure Cosmos DBを作成します。Azureのポータルから「Azure Cosmos DB」を選択し、「Azure Cosmos DBの作成」をクリックします。サービスの一覧に表示されない場合は、「その他のサービス」から検索できます。

f:id:ComponentOne_JP:20171106153412p:plain:w400

次にIDやAPIの設定などを行っていきます。「ID」にはユニークなIDを(今回は「ActiveReports-cosmosdb」としています)。「API」には「SQL (Document)」を。リソースグループは、既存のものがあればそれを指定してもいいですが、今回は「cosmosdb-group」というリソースグループを新規作成します。そのほか、「サブスクリプション」や「場所」は、自身の環境にあわせて設定してください。最後に「作成」をクリックするとデプロイメントが始まります。

f:id:ComponentOne_JP:20171106154842p:plain:w200

デプロイメントが完了すると、以下のようにCosmos DBのアカウントが作成されるので、「コレクションの追加」をクリックして、コレクションの作成に進みます。

f:id:ComponentOne_JP:20171106161923p:plain:w350

「コレクション ID」、「スループット」、「データベース」の入力項目を埋め(今回はそれぞれ、「TestCollection」、「400」、「TestDB」としています。)、「作成」をクリックします。

f:id:ComponentOne_JP:20171106162754p:plain:w200

「データ エクスプローラー」を開いて、登録されたコレクションを確認します。先ほど登録したデータベースとコレクションは以下のような階層構造になります。これらにさらにドキュメント(データ)がぶら下がる形になります。まだドキュメントは登録されていないので、「New Document」をクリックして、GUIからデータの登録を行っていきます。(なぜかデータ エクスプローラーのUIは英語です。)(実運用では、ドキュメントはAPIなどを使って登録するのがよいでしょう。)

f:id:ComponentOne_JP:20171106165435p:plain:w350

ドキュメントはJSON形式で保存されます。各ドキュメントには「id」を設定する必要があります。(設定しない場合は自動で採番されます) 今回は以下のような売上データをJSON化したものを登録してみます。

[
  {
    "id": "1",
    "CustomerID": "1",
    "Products": "コーヒー 250 ml",
    "Number": 100,
    "UnitPrice": 100,
    "BillNo": "WS-DF502",
    "Date": "2003-09-05T00:00:00",
    "SlipNo": "GB465",
    "EndDate": "2003-09-30T00:00:00",
    "CustomerName": "長崎カントリーフーズ"
  },
  {
    "id": "2",
    "CustomerID": "1",
    "Products": "紅茶 350 ml",
    "Number": 300,
    "UnitPrice": 120,
    "BillNo": "WS-DF502",
    "Date": "2003-09-05T00:00:00",
    "SlipNo": "GB465",
    "EndDate": "2003-09-30T00:00:00",
    "CustomerName": "長崎カントリーフーズ"
  },
  {
    "id": "3",
    "CustomerID": "1",
    "Products": "炭酸飲料 (オレンジ) 350 ml",
    "Number": 200,
    "UnitPrice": 120,
    "BillNo": "WS-DF502",
    "Date": "2003-09-05T00:00:00",
    "SlipNo": "DK055",
    "EndDate": "2003-09-30T00:00:00",
    "CustomerName": "長崎カントリーフーズ"
  },
  {
    "id": "4",
    "CustomerID": "2",
    "Products": "ボールペン (赤)",
    "Number": 50,
    "UnitPrice": 100,
    "BillNo": "WA-GJ419",
    "Date": "2003-09-04T00:00:00",
    "SlipNo": "PI198",
    "EndDate": "2003-09-30T00:00:00",
    "CustomerName": "(有)デジタルノロシネット"
  },
  {
    "id": "5",
    "CustomerID": "2",
    "Products": "ボールペン (青)",
    "Number": 100,
    "UnitPrice": 100,
    "BillNo": "WA-GJ419",
    "Date": "2003-09-13T00:00:00",
    "SlipNo": "TA471",
    "EndDate": "2003-09-30T00:00:00",
    "CustomerName": "(有)デジタルノロシネット"
  }
]

以下のようにJSONデータを入力します。ポータルからのドキュメントの登録は1件ずつ行うことになるので、「id」が「1」のJSONデータだけを入力し、「Save」をクリックして保存します。

f:id:ComponentOne_JP:20171106173556p:plain:w350

同様に「id」が「2」~「5」のJSONデータも登録します。登録したデータは「ドキュメント エクスプローラー」や「クエリエクスプローラー」で確認できます。

f:id:ComponentOne_JP:20171107171939p:plain:w350

以上でAzure Cosmos DB側の設定は完了です。

アプリケーションへのライブラリの追加

次にAzure Cosmos DBから取得したデータを、ActiveReports の帳票上に表示するアプリケーションを作成してみたいと思いますが、まずはプロジェクトを作成し、接続に必要なライブラリを追加します。

Visual Studio 2017を開き、「Visual C#」⇒「Reporting」のテンプレートから「ActiveReports 11.0J ページレポートアプリケーション」を選択します。プロジェクト名は「AzureCosmosTest」とします。 

f:id:ComponentOne_JP:20171107094957p:plain:w350

ソリューションエクスプローラーで、プロジェクトを右クリックし、[NuGet パッケージの管理] をクリックします。

f:id:ComponentOne_JP:20171107105517p:plain:w350

NuGet パッケージマネージャーのウィンドウが開いたら、 [参照] をクリックし、検索ボックスに「azure documentdb」と入力し、「Microsoft Azure DocumentDB」を選択し、[インストール] をクリックします。

f:id:ComponentOne_JP:20171107095924p:plain:w350

ソリューションの変更に関する確認と、ライセンスの同意に関する確認が表示されるので、それぞれ「OK」と「同意する」を をクリックします。以上で必要なライブラリがプロジェクトに追加されますが、この時追加される「Newtonsoft.Json」のバージョンが「6.0.8」になります。

ActiveReportsのdllも内部で「Newtonsoft.Json」を参照しているのですが、そちらは「7.0.1」を参照しているため、実行時にエラーが発生します。問題を回避するため、NuGet パッケージマネージャーから「インストール済み」をクリックし、「Newtonsoft.Json」のバージョンを「7.0.1」に更新してください。

f:id:ComponentOne_JP:20171108112252p:plain:w350

※最新のバージョンをインストールすることもできますが、その場合はApp.configの中で、使用するバージョンに合わせてbindingRedirectの設定が更新されていることを確認してください。

<dependentAssembly>
  <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
  <bindingRedirect oldVersion="0.0.0.0-10.0.0.3" newVersion="10.0.0.3" />
</dependentAssembly>

レポートファイルの作成

次にレポートファイルの作成を行います。プロジェクトを作成したときに自動生成されたPageReport1.rdlxを開き、「レポートエクスプローラ 11.0J」から「データソース」を右クリックし、「データソースの追加」を選択します。 (レポートエクスプローラが表示されていない場合は、「表示」-「その他のウィンドウ」-「ActiveReports 11.0J レポートエクスプローラ」のメニューから開けます。)

f:id:ComponentOne_JP:20171107132648p:plain:w350

「レポートデータソース」ダイアログで、「種類」に「Json Provider」、「JSONスキーマのソースの選択」に「埋め込み」を選択し、以下のJSONスキーマを入力し、「OK」をクリックすると「DataSource1」というデータソースが作成されます。 (本来であればここでJSONデータの設定も行うのですが、後ほどアプリケーション側でCosmosDBから取得したJSONデータを渡すので、今は省略します。)

f:id:ComponentOne_JP:20171107153227p:plain:w350

{
  "definitions": {}, 
  "$schema": "http://json-schema.org/draft-06/schema#", 
  "type": "array", 
  "$id": "http://example.com/example.json", 
  "items": {
    "type": "object", 
    "$id": "http://example.com/example.json/items", 
    "properties": {
      "id": {
        "type": "string", 
        "$id": "http://example.com/example.json/items/properties/id"
      }, 
      "CustomerID": {
        "type": "string", 
        "$id": "http://example.com/example.json/items/properties/CustomerID"
      }, 
      "Products": {
        "type": "string", 
        "$id": "http://example.com/example.json/items/properties/Products"
      }, 
      "Number": {
        "type": "integer", 
        "$id": "http://example.com/example.json/items/properties/Number"
      }, 
      "UnitPrice": {
        "type": "integer", 
        "$id": "http://example.com/example.json/items/properties/UnitPrice"
      }, 
      "BillNo": {
        "type": "string", 
        "$id": "http://example.com/example.json/items/properties/BillNo"
      }, 
      "Date": {
        "type": "string", 
        "$id": "http://example.com/example.json/items/properties/Date"
      }, 
      "SlipNo": {
        "type": "string",
        "$id": "http://example.com/example.json/items/properties/SlipNo"
      }, 
      "EndDate": {
        "type": "string", 
        "$id": "http://example.com/example.json/items/properties/EndDate"
      }, 
      "CustomerName": {
        "type": "string", 
        "$id": "http://example.com/example.json/items/properties/CustomerName"
      }
    }
  }
}

次に「レポートエクスプローラ 11.0J」から「DataSource1」を右クリックし、「データセットの追加」を選択します。

f:id:ComponentOne_JP:20171107173315p:plain:w350

「データセット」ダイアログで、「クエリ」ページからJSONクエリデザイナの編集ボタンをクリックし、「JSONクエリデザイナ」を開きます。[*]にカーソルを合わせダブルクリックし、「$.[*]」というクエリが作成されたら、「OK」をクリックしてダイアログを閉じます。

f:id:ComponentOne_JP:20171107174036p:plain:w350

完了するとデータセットにフィールドが追加されます。

f:id:ComponentOne_JP:20171107174842p:plain:w350

あとはデザイナ上にコントロールを配置して、レポートをデザインしていきます。データ連結する項目には先ほど作成したデータセットのフィールドを設定します。今回は以下のような請求書のレイアウトを作成しました。(本記事の最後で、作成したサンプルプロジェクトを公開しています。)

f:id:ComponentOne_JP:20171107175515p:plain:w350

Azure Cosmos DBとの連携処理の実装

Form1.csを開き、先頭に以下の参照を追加します。

using System.Linq;
using Microsoft.Azure.Documents.Client;
using Newtonsoft.Json;

次にForm1のコンストラクタの上に、以下の定数/変数を定義します。「EndpointUrl」と「PrimaryKey」には、それぞれの環境のエンドポイントURLとプライマリキーを設定してください。これらはAzureのポータルから作成したAzure Cosmos DBのアカウントを開き、「キー」のページから確認できます。

private const string EndpointUrl = "https://activereports-cosmosdb.documents.azure.com:443/";
private const string PrimaryKey = "<primary key>";
private DocumentClient client;

f:id:ComponentOne_JP:20171107103902p:plain:w350

Form1.csにAzure Cosmos DBに格納したオブジェクトを扱うためのクラスを追加します。

public class SalesData
{
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }
    public string CustomerID { get; set; }
    public string Products { get; set; }
    public int Number { get; set; }
    public int UnitPrice { get; set; }
    public string BillNo { get; set; }
    public DateTime Date { get; set; }
    public string SlipNo { get; set; }
    public DateTime EndDate { get; set; }
    public string CustomerName { get; set; }
    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);
    }
}

Form1.csにクエリの実行およびデータ成型の処理と、LocateDataSourceイベント発生時のイベントハンドラを追加します。

private string GetData(string databaseName, string collectionName)
{
    this.client = new DocumentClient(new Uri(EndpointUrl), PrimaryKey);
    IQueryable<SalesData> SalesDataQuery = this.client.CreateDocumentQuery<SalesData>(
            UriFactory.CreateDocumentCollectionUri(databaseName, collectionName), null);

    // クエリの実行
    // 取得したJSONデータを、ActiveReportsのJSONデータソースに読み込めるように成型する
    string AllData = "";
    foreach (SalesData Data in SalesDataQuery)
    {
        AllData = (AllData != "") ? AllData = AllData + ",": AllData;
        AllData = AllData + Data.ToString();
    }
    AllData = "[" + AllData + "]";
    return AllData;
}

private void OnLocateDataSource(object sender, GrapeCity.ActiveReports.LocateDataSourceEventArgs args)
{
    object data = null;
    var dataSourceName = args.DataSourceName;
    if (dataSourceName == "DataSource1")
    {
        data = GetData("TestDB", "TestCollection");
    }
    args.Data = data;
}

最後にForm1_Loadイベントの中身を以下のように書き換えます。

private void Form1_Load(object sender, EventArgs e)
{
    var rptPath = new System.IO.FileInfo(Application.StartupPath + @"\..\..\PageReport1.rdlx");
    var definition = new GrapeCity.ActiveReports.PageReport(rptPath);
    // LocateDataSourceイベントにイベントハンドラを関連付けします。    
    definition.Document.LocateDataSource += OnLocateDataSource;
    viewer1.LoadDocument(definition.Document);
}

実行結果

プロジェクトを実行すると、ActiveReportsのビューワ上にレポートが表示されます。一覧に表示されているデータはAzure Cosmos DBから取得したものです。また今回実装方法の説明は省略していますが、データの読み取り先のリージョンの情報を赤字でレポートのヘッダに表示しています。もしレプリケーションを行っていれば、ConnectionPolicy.PreferredLocationsプロパティから読み取り先のリージョンを指定するこも可能です。もし興味がありましたらサンプルの中のソースコードで説明しているのでご覧ください。

f:id:ComponentOne_JP:20171110105136p:plain:w350

記事で作成したプロジェクトはこちらで公開しています。 実行前にNuGetパッケージの復元と、DBへの接続情報の変更を行ってください。

ダウンロード(zipファイル:16.3KB)