はじめに
ExcelからDynamoDBにアクセスしてデータを取得するツールを作ってみた。
QiitaなどWEB上に記事を投稿するのは初めてなので、練習兼備忘として色々書いてみる。
実現方法を考える
DynamoDBのデータを取得するには色々な手段があるが、今回はExcelを起点とするためVBAから実行できる処理が必要だった。
AWS系のサービスにプログラムからアクセスするとき真っ先に思いつくのはAWS SDKだが、VBA用のものは見当たらない。
他の手段としてはREST APIでデータを取得出来そうだったが、めんどくさそうな気配がしたのでやりたくない…
結果、AWS SDKが用意されているC#.NETを用いてVBAから呼び出せるDLLを作成し、それ経由でデータを取得することにした。
とりあえず作業開始
C#.NETで書くと言っても、実はJavaエンジニアな私はサラッと書けず、ネット上の記事を参考にしながらのコーディングだった。
Visual Studio 2022をインストールし、C#用のクラスライブラリ(.NET Framework)プロジェクトのテンプレートを使ってプロジェクトを作成する。
プロジェクトプロパティを変更する
次にDLLを使用するための設定を行う。(あまり理解できてないが、これを行わないとDLLとして使えないらしい)
画面最上部のメニューバーから、「プロジェクト」 > 「(プロジェクト名)のプロパティ」をクリックすると下記のような画面が開くので2カ所設定を変える。
まずは「アプリケーション」タブから「アセンブリ情報」を開き、「アセンブリをCOM参照可能にする」にチェックを入れる。
次に、「ビルド」タブを開き「COM相互運用機能の登録」にチェックを入れる。
※これに関しては必須ではなく、別途コマンドを打てば代替可能らしい。
設定し終えたらCtrl + S
で設定を保存して完了だ。
AWS SDKのインストール
続いて、DynamoDBにアクセスするためのAWS SDKをインストールする。
画面最上部のメニューバーから、「プロジェクト」 > 「NuGetパッケージの管理」をクリックすると下記のような画面が開く。
検索欄にdynamodb
と入力して、AWSSDK.DynamoDBv2
をインストールする。
依存している必須パッケージであるAWSSDK.Core
は同時にインストールされるので別途インストールする必要はない。
コーディングしてみる
必須のセットアップは完了したので、拙いながらもコーディングをしてみる。
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
namespace DynamoDBAccessQiita
{
[ComVisible(true)]
public interface IDynamoDBAccessQiita
{
[Description("DynamoDBから設定値を取得する")]
List<string[]> getDynamoDBData(string partitionKeyValue);
}
[ClassInterface(ClassInterfaceType.None)]
public class GetData : IDynamoDBAccessQiita
{
private static readonly string[] attributeNames = new string[]
{
"PKey",
"SKey",
"Col1",
"Col2"
};
public List<string[]> getDynamoDBData(string partitionKeyValue)
{
AmazonDynamoDBClient dynamoDBClient = new AmazonDynamoDBClient(
new BasicAWSCredentials("AccessKeySample", "SecretKeySample"),
new AmazonDynamoDBConfig
{
RegionEndpoint = Amazon.RegionEndpoint.APNortheast1
});
// クエリの作成
QueryRequest request = new QueryRequest
{
TableName = "SampleTable",
KeyConditionExpression = "#pk = :pk",
ExpressionAttributeNames = new Dictionary<string, string>
{
{ "#pk", "PKey" }
},
ExpressionAttributeValues = new Dictionary<string, AttributeValue>
{
{ ":pk", new AttributeValue { S = partitionKeyValue } }
}
};
// クエリの実行
QueryResponse response = dynamoDBClient.Query(request);
var result = new List<string[]>();
// 取得したレコードを返却用に詰め替え
foreach (var item in response.Items)
{
var record = new string[attributeNames.Length];
for (int i = 0; i < attributeNames.Length; i++)
{
record[i] = item.TryGetValue(attributeNames[i], out var value) ? value.S : null;
}
result.Add(record);
}
return result;
}
}
}
説明用のコードなので簡略化しているが、本来はもう少しまともなコードなのでお目汚しご容赦願いたい。
この状態でビルドすると以下のような警告が出る。
warning : タイプ ライブラリ エクスポーターで '○○○○' を処理中に警告が出されました。警告: タイプ ライブラリ エクスポーターでシグネチャのジェネリック型インスタンスが見つかりました。ジェネリック コードを COM にエクスポートすることはできません。
分からない単語だらけで解決に時間がかかったが、ジェネリック型
というのが諸悪の根源のようだ…
ジェネリック型を使うことはできない
前述のコードは、取得したデータをList
に格納して呼び出し元に返そうとするコードである。
このList
、よくよく見てみると名前空間がSystem.Collections.Generic
となっている。
どうやらこれに属しているクラスがジェネリック型
と呼ばれるものらしい。
List<string[]>
のように要素の型を指定出来るものであり、普段List構造を使うならこの書き方だろう。
しかし、DLLとしてはList
で返すことができないようなので、非ジェネリック型であるArrayList
を使うことでこの問題を解消できる。
違いについては別途調べてもらいたいが、大して違いはなさそうなので単純に置き換えてみる。
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
namespace DynamoDBAccessQiita
{
[ComVisible(true)]
public interface IDynamoDBAccessQiita
{
[Description("DynamoDBから設定値を取得する")]
ArrayList getDynamoDBData(string partitionKeyValue);
}
[ClassInterface(ClassInterfaceType.None)]
public class GetData : IDynamoDBAccessQiita
{
private static readonly string[] attributeNames = new string[]
{
"PKey",
"SKey",
"Col1",
"Col2"
};
public ArrayList getDynamoDBData(string partitionKeyValue)
{
AmazonDynamoDBClient dynamoDBClient = new AmazonDynamoDBClient(
new BasicAWSCredentials("AccessKeySample", "SecretKeySample"),
new AmazonDynamoDBConfig
{
RegionEndpoint = Amazon.RegionEndpoint.APNortheast1
});
// クエリの作成
QueryRequest request = new QueryRequest
{
TableName = "SampleTable",
KeyConditionExpression = "#pk = :pk",
ExpressionAttributeNames = new Dictionary<string, string>
{
{ "#pk", "PKey" }
},
ExpressionAttributeValues = new Dictionary<string, AttributeValue>
{
{ ":pk", new AttributeValue { S = partitionKeyValue } }
}
};
// クエリの実行
QueryResponse response = dynamoDBClient.Query(request);
var result = new ArrayList();
// 取得したレコードを返却用に詰め替え
foreach (var item in response.Items)
{
var record = new string[attributeNames.Length];
for (int i = 0; i < attributeNames.Length; i++)
{
record[i] = item.TryGetValue(attributeNames[i], out var value) ? value.S : null;
}
result.Add(record);
}
return result;
}
}
}
再度ビルドしてみると、今度はエラーも警告もなしで完了した。
一旦DLL側はこの状態で置いておいて、VBA側の対応を行う。
VBA側でやること
まずはDLLをVBAから呼び出せるようにする。
VBAのエディタを開き、画面上部のメニューバーから、「ツール」 > 「参照設定」をクリックすると下記のような画面が開くので、先ほど作成したDLLにチェックを入れてOKボタンを押下するだけで良い。
次にDLL経由でDynamoDBのデータを取得し、シートに書き出すマクロを書く。
Option Explicit
Sub GetDynamoDB()
Dim dll As DynamoDBAccess.GetData
Set dll = New DynamoDBAccess.GetData
Dim records As Object
Dim record As Variant
Dim rowIndex As Integer
' 外部DLL経由でDynamoDBの値を取得
Set records = dll.getDynamoDBData("hoge")
' 取得したリストをシートに出力
For Each record In records
ActiveSheet.Range("A1").Offset(rowIndex).Resize(, 4) = record
rowIndex = rowIndex + 1
Next record
End Sub
試しに動かしてみたらちゃんと取得できたので、今回はここまで。
最後に
本来であれば、取得処理部分では対象のデータ型ごとに異なる処理をしなければならないのだが、今回は説明用に文字列だけを対象としたコードにしている。
また、使用しているExcelが32ビット用か64ビット用かでもビルドオプションを変えなければならないなど、説明を省略している部分が多々ある。
今後もDLLを作成する機会があれば、そのあたりも補足を入れて記事にしたい。