MLBお兄さんこと松村です。弊社オフィスのある福岡県も徐々に寒くなってきました。秋が終わっちゃいましたね。
11月中頃に Azure Functions のバインディング可能なリソースに Azure SQL Databaase が追加されました。これを試してみます。
なお、記事執筆時点ではプレビューで C# のみ提供されています。
バインドとは
Azure Functions の「バインド(バインディング)」とは、関数に他のリソースを宣言的に接続するための方法です。
バインドしたリソースはアクセス可能な状態で使用することができ、入力方向(取得)と出力方向(保存)の操作を行うことができます。
Azure SQL バインドの場合は以下の挙動になります。今回は入力バインドについて試してみます。
- 入力バインド:データベースからデータを読み取る
- 出力バインド:データベースにデータを保存する
セットアップ
拡張機能のインストール
Azure Functions のプロジェクトを作成したら、次の NuGet パッケージをインストールします。
接続文字列の追加
開発環境では local.settings.json
に Azure SQL の接続文字列を追加します。
設定名は後述のプログラム側の定義と一致させます。
{ ..., "ConnectionStrings": { "SqlConnectionString": "{Azure SQLの接続文字列}" } }
バインド定義
Azure SQL バインドを使用するには Microsoft.Azure.WebJobs.SqlAttribute
の属性付きの引数を用意します。
public IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Sql("select * from users where name = @Name", CommandType = System.Data.CommandType.Text, Parameters = "@Name={Query.name}", ConnectionStringSetting = "SqlConnectionString")] IEnumerable<User> users)
Sql
属性の設定項目は以下の通りです。
- コンストラクタ引数 : バインド時に実行したい SQL コマンド
- パラメーターは
@
を先頭につける
- パラメーターは
CommandType
: コマンドの種別(純粋な SQL 文やストアドプロシージャ)Parameter
: SQL コマンドで指定しているパラメーターを指定する- HTTP トリガーのクエリパラメーターを使用する場合は
{Query.クエリパラメーター名}
とする - HTTP トリガーのパスパラメーターを使用する場合は
{パスパラメーター}
とする
- HTTP トリガーのクエリパラメーターを使用する場合は
ConnectionStringSetting
: local.settings.json や環境変数に定義している Azure SQL への接続文字列
それではいくつかサンプルを書いたので載せていきます。
なおデータベースは Azure SQL 作成時に指定できるサンプルデータベースを使用しています。
純粋な SELECT 文
[FunctionName("GetAllProducts")] public IActionResult GetAll( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Sql("select * from SalesLT.Product order by ProductId asc", CommandType = System.Data.CommandType.Text, Parameters = "", ConnectionStringSetting = "SqlConnectionString")] IEnumerable<Product> products) { return new OkObjectResult(products); }
curl -X GET "http://localhost:7071/api/GetAllProducts" -H "accept: application/json" [ { "productID": "680", "name": "HL Road Frame - Black, 58", "productNumber": "FR-R92B-58", "color": "Black", ... }, { "productID": "706", "name": "HL Road Frame - Red, 58", "productNumber": "FR-R92R-58", "color": "Red", ... } ]
条件指定
[FunctionName("GetProducts")] public IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Sql("select * from SalesLT.Product where Color = @Color order by ProductId asc", CommandType = System.Data.CommandType.Text, Parameters = "@Color={Query.color}", ConnectionStringSetting = "SqlConnectionString")] IEnumerable<Product> products) { return new OkObjectResult(products); }
curl -X GET "http://localhost:7071/api/GetProducts?color=Red" -H "accept: application/json" [ { "productID": "706", "name": "HL Road Frame - Red, 58", "productNumber": "FR-R92R-58", "color": "Red", ... }, ... ]
カウント
[FunctionName("GetProductsCount")] public IActionResult GetCount( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Sql("select count(ProductId) cnt from SalesLT.Product where Color = @Color", CommandType = System.Data.CommandType.Text, Parameters = "@Color={Query.color}", ConnectionStringSetting = "SqlConnectionString")] IEnumerable<ProductCount> products) { return new OkObjectResult(products.FirstOrDefault()); }
curl -X GET "http://localhost:7071/api/GetProductsCount?color=Red" -H "accept: application/json" { "cnt": 38 }
View テーブルへの SELECT
[FunctionName("GetCategoryView")] public IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Sql("select * from SalesLT.vGetAllCategories where ParentProductCategoryName = @Name", CommandType = System.Data.CommandType.Text, Parameters = "@Name={Query.name}", ConnectionStringSetting = "SqlConnectionString")] IEnumerable<CategoryView> categories) { return new OkObjectResult(categories); }
curl -X GET "http://localhost:7071/api/GetCategoryView?name=Accessories" -H "accept: application/json" [ { "parentProductCategoryName": "Accessories", "productCategoryName": "Bike Racks", "productCategoryID": 30 }, { "parentProductCategoryName": "Accessories", "productCategoryName": "Bike Stands", "productCategoryID": 31 }, ... ]
JOIN 句
クエリが長い場合は定数化するのがオススメです。
また、この例では条件指定にパスパラメーターを使用しています。
private const string COMMAND_TEXT = @"SELECT H.SalesOrderID, D.SalesOrderDetailId, D.OrderQty FROM SalesLT.SalesOrderHeader H INNER JOIN SalesLT.SalesOrderDetail D ON (H.SalesOrderID = D.SalesOrderID) WHERE H.SalesOrderID = @Id"; [FunctionName("GetSalesOrder")] public IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "GetSalesOrder/{id}")] HttpRequest req, [Sql(COMMAND_TEXT, CommandType = System.Data.CommandType.Text, Parameters = "@Id={id}", ConnectionStringSetting = "SqlConnectionString")] IEnumerable<SalesOrder> salesOrders) { return new OkObjectResult(salesOrders); }
curl -X GET "http://localhost:7071/api/GetSalesOrder/71774" -H "accept: application/json" [ { "salesOrderID": 71774, "salesOrderDetailId": 110562, "orderQty": 1 }, { "salesOrderID": 71774, "salesOrderDetailId": 110563, "orderQty": 1 } ]
長くなってしまいましたが、一般的な SELECT 文であれば問題なく実行することができそうです。
上記のサンプルコードはこのリポジトリに置いています。参考にしてみてください。
これまでは EntityFramework Core のような O/R マッパーを用いてクエリを発行していましたが、 Azure SQL バインドを使うことでコードがよりシンプルになります。
次回は出力バインドを試してみたいと思います。