AWS DynamoDBをC#でCRUDする

今回はDynamoDBへのCRUD操作を、C#で書いて、Lambdaで動かしてみたいと思います。

テーブルの作成

まずはAWS ConsoleからDynamoDBへテーブルを予め作成します。

  • テーブル名を"Todos"とします。
  • パーティションキーを"Id"、ソートキーを"Due"とします。 f:id:ohke:20170106235004j:plain:w450

エンティティクラスの作成

AWS Lambdaプロジェクトを作成し、今回はDynamoDBTable属性を付与したエンティティクラスを使います。

  • AWS Lambdaプロジェクトの作成とデプロイは下記の投稿に詳しく記載されています。

qiita.com

  • DynamoDBTable属性が付与することでTodoEntityクラスがテーブル("Todos")へマッピングされます。
using System.Collections.Generic;
using Amazon.DynamoDBv2.DataModel;

namespace AWSLambda
{
    [DynamoDBTable("Todos")]
    public class TodoEntity
    {
        // パーティションキー(ハッシュキー)
        [DynamoDBHashKey]
        public string Id { get; set; }

        // ソートキー(レンジキー)
        [DynamoDBRangeKey]
        public string Due { get; set; }

        // テーブルでは項目名"Done"にマッピングされる
        [DynamoDBProperty("Done")]
        public bool? DoneFlag { get; set; }

        // List<T>とすることで複数項目にマッピングされる
        public List<string> Contents { get; set; }

        // テーブルへマッピングされない
        [DynamoDBIgnore]
        public string Operation { get; set; }
    }
}

Lambdaの作成

それではLambdaを作成します。

  • TodosテーブルへマッピングされたTodoEntityオブジェクトを介すことで、DynamoDBへ接続されたDynamoDBContextを使って読み書きします。
  • TodoEntityクラスを引数として、Operationの値によってInsert/Select/Update/Deleteの処理を分岐しています。
  • いずれの処理もパーティションキーとソートキーの指定が必要です。
using Amazon;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Amazon.Lambda.Core;
using Newtonsoft.Json;

namespace AWSLambda
{
    public class DynamoDbOperationFunction
    {
        private static readonly AmazonDynamoDBClient Client = new AmazonDynamoDBClient(RegionEndpoint.USWest2);

        // JSONをデシリアライズしてTodoEntityオブジェクトを得る
        [LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
        public void DynamoDbOperationHandler(TodoEntity entity, ILambdaContext context)
        {
            context.Logger.LogLine(JsonConvert.SerializeObject(entity));

            // 最初にDynamoDBContextを生成する
            using (var dbContext = new DynamoDBContext(Client))
            {
                switch (entity.Operation)
                {
                    case "Insert":
                        // SaveAsyncメソッドでentityがTodosテーブルへ追加
                        var insertTask = dbContext.SaveAsync(entity);
                        insertTask.Wait();
                        break;

                    case "Select":
                        // LoadAsyncメソッドでパーティションキーとソートキーが一致するレコードを取得
                        var selectTask = dbContext.LoadAsync<TodoEntity>(hashKey: entity.Id, rangeKey: entity.Due);
                        selectTask.Wait();
                        context.Logger.LogLine(JsonConvert.SerializeObject(selectTask.Result));
                        break;

                    case "Update":
                        // 更新対象のレコードをLoadAsyncで取得して、
                        var updateTask1 = dbContext.LoadAsync<TodoEntity>(hashKey: entity.Id, rangeKey: entity.Due);
                        updateTask1.Wait();
                        var updatedEntity = updateTask1.Result;

                        // 更新後のレコードをSaveAsyncでテーブルに追加する
                        // (すなわち、パーティションキーとソートキーが一致する場合は上書きされる)
                        updatedEntity.Contents = entity.Contents ?? updatedEntity.Contents;
                        updatedEntity.DoneFlag = entity.DoneFlag ?? updatedEntity.DoneFlag;
                        var updateTask2 = dbContext.SaveAsync(entity);
                        updateTask2.Wait();
                        break;

                    case "Delete":
                        // DeleteAsyncメソッドでパーティションキーとソートキーが一致するレコードを削除
                        var deleteTask = dbContext.DeleteAsync<TodoEntity>(hashKey: entity.Id, rangeKey: entity.Due);
                        deleteTask.Wait();
                        break;
                }
            }
        }
    }
}

動作確認

それではAWS Explorerからデプロイして動作確認します。

Insertでは以下のような引数用のJSONを作成し、

{
    "Id": "001",
    "Due": "2017-01-10T10:00:00",
    "DoneFlag": false,
    "Contents": [
        "abcde",
        "12345"
    ],
    "Operation": "Insert"
}

AWS Explorerから実行してみます。 f:id:ohke:20170106235017j:plain

そしてAWS ConsoleからDynamoDBのTodosテーブルを開くと、Idが"001"のレコードが作成されていることがわかるかと思います。

  • Contentsに複数項目、またOperationが無いことも確認してください。

f:id:ohke:20170106235538j:plain:w450

同じようにSelectも以下のような引数用のJSONを作成し、

{
    "Id": "001",
    "Due": "2017-01-10T10:00:00",
    "Operation": "Select"
}

AWS Explorerから実行すると参照できていることも確認できるかと思います。
f:id:ohke:20170106235837j:plain:w450

UpdateではDoneFlagとContentsを変更した引数を渡しており、

{
    "Id": "001",
    "Due": "2017-01-10T10:00:00",
    "DoneFlag": true,
    "Contents": [
        "ABCDE",
        "54321"
    ],
    "Operation": "Update"
}

それぞれ更新されていることが確認できます。 f:id:ohke:20170107000028j:plain:w450

Deleteもパーティションキーとソートキーを渡すだけです。

{
    "Id": "001",
    "Due": "2017-01-10T10:00:00",
    "Operation": "Delete"
}

まとめ

今回はDynamoDBTable属性とDynamoDBContextを使ってDynamoDBを操作しましたが、ScanやQuery、BatchWrite、BatchGetなども提供されています。
これ以外にもドキュメントモデルや低レベルAPIを使って操作する方法もありますので、今後紹介できればと思います。