くうと徒然なるままに

モバイルアプリを作りながらバックエンドも作っています。

Azure Function + Azure Table Storage で CRUD してみる

ハッカソンで必要な知識だったので試した記録

Azure Function とは

今、話題のサーバーレスとか呼ばれてるやつ。開発者はコードに書くことに集中でき、それ以外は Azure にお任せできる

Visual Studio 2017.3 に標準で Azure Function を開発するための拡張機能が追加されたので触ってみた感じ。

言語は C# を選んだ。 NodeJS を前提に作られてるっぽいけど、今回は Visual Studio 2017 の進化したインテリセンスを使ってみたくて、、、

Azure Table Storage とは

いわゆる、 NoSQL なデータベースで、 Key-Value な感じを採用してる。 今回は、 Azure Function と標準で対応してて相性がよさそうという理由で採用してる

CRUD

データベースに対する基本的な操作の データの作成 © 読み取り ® 更新 (U) 削除 (D) なのをまとめてそう呼んでいます。

実際にどうやるのか

今回操作していくデータ

テーブル名

storeInfo

カラム

  • Name
  • Locationx
  • Locationy

データの作成 ©

作成するテンプレート

Http-POST なテンプレートを使用して作成していきます。

f:id:kuxumarin:20170816140609p:plain

コード例

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.WindowsAzure.Storage.Table;

namespace FunctionApp1
{
    public static class PostStoreData
    {
        [FunctionName("PostStoreData")]
        public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequestMessage req, [Table("storeInfo", Connection = "")]ICollector<Person> outTable, TraceWriter log)
        {
            dynamic data = await req.Content.ReadAsAsync<object>();
            string name = data?.name;
            double locationx = data?.locationx;
            double locationy = data?.locationy;

            if (name == null || locationx == 0 || locationy == 0)
            {
                return req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name in the request body");
            }

            outTable.Add(new Person()
            {
                PartitionKey = "Functions",
                RowKey = Guid.NewGuid().ToString(),
                Name = name,
                LocationX = locationx,
                LocationY = locationy
            });
            return req.CreateResponse(HttpStatusCode.Created);
        }

        public class Person : TableEntity
        {
            public string Name { get; set; }
            public double LocationX { get; set; }
            public double LocationY { get; set; }
        }
    }
}

データの読み込み ®

使用するテンプレート

Http-GET なテンプレートを使用して作りました。

f:id:kuxumarin:20170816174759p:plain

コード例

using System.Linq;
using System.Net;
using System.Net.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.WindowsAzure.Storage.Table;

namespace FunctionApp1
{
    public static class GetStoreData
    {
        [FunctionName("GetStoreData")]
        public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get")]HttpRequestMessage req, [Table("storeInfo", Connection = "")]IQueryable<Person> inTable, TraceWriter log)
        {
            var query = from person in inTable select person;
            
            foreach (Person person in query)
            {
                log.Info($"Name:{person.Name}");
            }
            return req.CreateResponse(HttpStatusCode.OK, inTable.ToList());
        }

        public class Person : TableEntity
        {
            public string Name { get; set; }
            public double LocationX { get; set; }
            public double LocationY { get; set; }
        }
    }
}

データの更新 (U)

Http-PUT なテンプレートを使用し作りました

f:id:kuxumarin:20170816175006p:plain

コード例

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.WindowsAzure.Storage.Table;
using Newtonsoft.Json;
using System.Linq;

namespace FunctionApp1
{
    public static class UpdateStoreInfo
    {
        [FunctionName("UpdateStoreInfo")]
        public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "put")]Person person, [Table("storeInfo", Connection = "")]CloudTable outTable, TraceWriter log)
        {
            if (string.IsNullOrEmpty(person.Name))
            {
                return new HttpResponseMessage(HttpStatusCode.BadRequest)
                {
                    Content = new StringContent("A non-empty Name must be specified.")
                };
            };

            log.Info($"PersonName={person.Name}");

            //outTable
            //    .CreateQuery<Person>()
            //    .Where(personData => personData.Name == "kuxu")
            //    .Select(personData => personData.PartitionKey)
            //    .ToList()
            //    .ForEach(partitionKey => ;

            TableOperation updateOperation = TableOperation.InsertOrReplace(person);
            TableResult result = outTable.Execute(updateOperation);
            return new HttpResponseMessage((HttpStatusCode)result.HttpStatusCode);
        }

        public class Person : TableEntity
        {
            public string Name { get; set; }
            public double LocationX { get; set; }
            public double LocationY { get; set; }
        }
    }
}

データの削除 (D)

Http-Trigger なテンプレートをもとに作りました。

f:id:kuxumarin:20170816175142p:plain

コード例

using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using static FunctionApp1.PostStoreData;
using System;
using Microsoft.WindowsAzure.Storage.Table;

namespace FunctionApp1
{
    public static class DeleteStoreInfo
    {
        [FunctionName("DeleteStoreInfo")]
        public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "delete", Route = null)]HttpRequestMessage req, [Table("storeInfo", Connection = "")] CloudTable inTable, TraceWriter log)
        {
            log.Info("C# HTTP trigger function processed a request.");

            dynamic data = await req.Content.ReadAsAsync<object>();

            string key = data?.key;
            string rowNumber = data?.rowNumber;

            if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(rowNumber))
                return req.CreateResponse(HttpStatusCode.BadRequest, "Argument Erro");

            var excuseResult = inTable.Execute((TableOperation.Delete(new TableEntity() { PartitionKey = key, ETag = "*"})));

            HttpStatusCode stateCode;

            if (excuseResult.HttpStatusCode.ToString().StartsWith("2"))
            {
                stateCode = HttpStatusCode.Accepted;
            }
            else
            {
                stateCode = HttpStatusCode.BadRequest;
            }
                
            return req.CreateResponse(stateCode, $"Code:{excuseResult.HttpStatusCode}");
        }
    }
}

値で検索してデータを返す

Http-GET なテンプレートを使用して作成

// 画像は省略

実行例

今回は、 データベースの中にすでに存在している name プロパティを参考に検索するコードを書いていきます。

検索するワードの指定は、 GET リクエストのURLの最後に name={検索したい文字} で指定しています。

既存のデータ

f:id:kuxumarin:20170817075847p:plain

検索結果

検索ワード: kuxu f:id:kuxumarin:20170817080043p:plain

検索ワード: chihiro f:id:kuxumarin:20170817080004p:plain

コード例

using System.Linq;
using System.Net;
using System.Net.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.WindowsAzure.Storage.Table;

namespace FunctionApp1
{
    public static class SearchStoreInfo
    {
        [FunctionName("SearchStoreInfo")]
        public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get")]HttpRequestMessage req, [Table("storeInfo", Connection = "")]IQueryable<Person> inTable, TraceWriter log)
        {
            dynamic data = req.Content.ReadAsAsync<object>();


            string name = req.GetQueryNameValuePairs()
                             .FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
                             .Value;

            if (string.IsNullOrWhiteSpace(name))
                return req.CreateResponse(HttpStatusCode.BadRequest, "Argument Not found");

            var searchResultList = inTable.Where(person => person.Name == name).ToList();

            if (searchResultList.Count == 0)
                return req.CreateErrorResponse(HttpStatusCode.NotFound, "Data Not Found");

            return req.CreateResponse(HttpStatusCode.OK, searchResultList);
        }

        public class Person : TableEntity
        {
            public string Name { get; set; }
        }
    }
}

感想

ちゃっと作るときは便利ですね~ 目的によっては強い子かも