Azure Functions - Entity Framework
Introduction
As part of the CQRS in Azure MSDN blog series, a lightweight Azure Function was created to insert into an inventory table. This article describes how this was done.
The source can be found on MSDN Samples: here.
Visual Studio Azure Function Template (preview)
The VS Azure Function Template was used to generate the basic project and add a new Azure Function called UpdateInventory:
The function itself is a simple HTTP trigger that receives a message payload either in the body or as parameters. Its signature (function.json) is shown below:
{
"bindings": [
{
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"post"
],
"authLevel": "function"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"disabled": false
}
project.json
The next step was to add the EntityFramework and System.Data.Common NuGet packages to project.json.
{
"frameworks": {
"net46":{
"dependencies": {
"EntityFramework": "6.1.2",
"System.Data.Common": "4.3.0"
}
}
}
}
Note: after deploying the project the download of the packages did not automatically happen and the file had to be "touched" again to trigger this to happen.
appsettings.json
The database connection string is configured in application settings and is shown below:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "...",
"AzureWebJobsDashboard": "...",
"SQLAZURECONNSTR_PieShopConnectionString": "Data Source=..."
}
}
run.csx
The function supports both receiving a JSON payload as well as receiving the content as parameters. This is shown in line 11 of the source when the content is not read as the defined object. The insert command (lines 35-40) simply uses the connection string to open a connection to the database. The command, an insert, is then constructed and sent.
Note: the casting of the received parameters is an intention to prevent any SQL injection attacks.
Note: the capability to send the update information as parameters were introduced due to CORS. The underlying jquery $.ajax translated the data payload automatically when the dataType 'jsonp' was used.
using System;
using System.Linq;
using System.Net;
using System.Data;
using System.Data.Entity;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
InventoryEntryData entry = await req.Content.ReadAsAsync<InventoryEntryData>();
if (entry == null)
{
// were they sent in query?
var parameters = req.GetQueryNameValuePairs().ToList();
var ingredient = parameters.Where(parameter => parameter.Key == "IngredientId").Select(parameter => parameter.Value);
var inventory = parameters.Where(parameter => parameter.Key == "InventoryId").Select(parameter => parameter.Value);
var quantity = parameters.Where(parameter => parameter.Key == "Quantity").Select(parameter => parameter.Value);
if(ingredient.Count() == 1 && inventory.Count() == 1 && quantity.Count() == 1)
{
entry = new InventoryEntryData
{
IngredientId = int.Parse(ingredient.First()),
InventoryId = int.Parse(inventory.First()),
Quantity = int.Parse(quantity.First()),
};
}
else
return req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a valid InventoryEntry in the request body");
}
try
{
using (var context = new DbContext(System.Environment.GetEnvironmentVariable("SQLAZURECONNSTR_PieShopConnectionString")))
{
context.Database.Connection.Open();
context.Database.ExecuteSqlCommand(string.Format("INSERT INTO ledger.InventoryEntries(InventoryId, IngredientId, Quantity) VALUES ({0}, {1}, {2}) ", entry.InventoryId, entry.IngredientId, entry.Quantity));
context.Database.Connection.Close();
}
}
catch(System.Data.Entity.Infrastructure.DbUpdateException ex)
{
log.Info(string.Format("Failure with database update {0}.", ex.Message));
return req.CreateResponse(HttpStatusCode.BadRequest, string.Format("Failure updating inventory. Please verify the ingredient id {0} inventory id {1} and quantity {2} are correct.", entry.IngredientId, entry.InventoryId, entry.Quantity));
}
catch(Exception ex)
{
log.Info(string.Format("Failure during processing {0}.", ex.Message));
return req.CreateResponse(HttpStatusCode.InternalServerError, "UpdateInventory api is currently not available.");
}
return req.CreateResponse(HttpStatusCode.Created);
}
public class InventoryEntryData
{
public int InventoryId { get; set; }
public int IngredientId { get; set; }
public int Quantity { get; set; }
}
Summary and References
This article was intended to illustrate how Entity Framework can be used in Azure Functions simply by referencing the required NuGet packages. The combination of lightweight microservices and SQL Azure allows for simple scalable services that can be developed and managed outside of traditional monolithic solutions.