REST API on Azure Functions with Entity Framework

Introduction

Setting up a REST API on Azure is a popular choice. Azure functions stand out for their speed, cost-effectiveness, and lightweight nature. Yet, integrating databases with Azure functions can pose challenges, especially with the lack of out-of-the-box support for Entity Framework. In this article, we’ll explore the process of developing an HTTP REST API on Azure functions with Entity Framework backed Azure PostgreSQL database. We are going to develop a REST API to support basic CRUD operations for a customer database. [Hosting the Azure function app and Postgre SQL database creation is out of scope for this article]

Setup the new project

Here we are using Visual Studio to create a new project with Azure Functions template and .NET 6.

create project for azure function with entity framework

When you create a functions app project in Visual Studio, it generates a default Function class named Function1.cs. This serves as the fundamental structure for a function, and a function app can comprise one or multiple functions. In this article, we are going to implement multiple functions to deal with a database.

While it’s possible to have multiple function classes, for this demonstration, we’ll stick to one class housing multiple functions for the sake of simplicity. So, let’s go ahead and rename Function1.cs to CustomerApi.cs.

Adding support for Entity Framework and PostgreSQL

As we already know, Azure functions does not support EF and PosgreSQL out-of-the-box. So, we need to add this support explicitely. First we need to install two required packages to access the database.

 <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.0" />

<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
The above first package is the EF core for PostgreSQL management and the second one is to support dependency injection as we need to inject the db context into our API functions class. 

Then add a new class for db context.

using Microsoft.EntityFrameworkCore;

namespace CustomersFunctionApi
{
public class DataContext : DbContext
{
private readonly string? _connectionString;
public DataContext(DbContextOptions<DataContext> options, string? connectionString) : base(options)
{
_connectionString = connectionString;
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseNpgsql(_connectionString);
}

public DbSet<Customer> Customers { get; set; }
}
}

Changes to the Api Class

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Threading.Tasks;

namespace CustomersFunctionApi
{
public class CustomerApi
{
private readonly DataContext _dataContext;

public CustomerApi(DataContext dataContext)
{
_dataContext = dataContext;
}

[FunctionName("GetCustomers")]
public async Task<IActionResult> GetAllCustomers(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "customers")] HttpRequest req,
ILogger log)
{
log.LogInformation("Processing GetCustomers request.");
try
{
var customers = await _dataContext.Customers.AsNoTracking().ToListAsync();
return new OkObjectResult(customers);
}
catch (Exception ex)
{
return new ObjectResult(ex) { StatusCode = 500, Value = ex.Message };
}
}

[FunctionName("CreateCustomer")]
public async Task<IActionResult> CreateCustomer(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "customers")] HttpRequest req,
ILogger log)
{
log.LogInformation("Processing CreateCustomer request.");
try
{
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
log.LogInformation("Request : {req}", requestBody);
var customer = JsonConvert.DeserializeObject<Customer>(requestBody);

var createdCustomer = new Customer
{
CustomerId = string.IsNullOrWhiteSpace(customer.CustomerId) ? Guid.NewGuid().ToString() : customer.CustomerId,
CreatedAt = DateTime.Now.ToUniversalTime(),
Email = customer.Email,
FirstName = customer.FirstName,
LastName = customer.LastName,
Phone = customer.Phone
};

_dataContext.Customers.Add(createdCustomer);
await _dataContext.SaveChangesAsync();
return new CreatedResult($"/customers/{createdCustomer.CustomerId}", createdCustomer);
}
catch (Exception ex)
{
return new ObjectResult(ex) { StatusCode = 500, Value = ex.Message };
}
}
}
}

Read Connection string from appsettings

Add a Startup class to read the settings from appsettings json

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;

[assembly: FunctionsStartup(typeof(CustomersFunctionApi.Startup))]
namespace CustomersFunctionApi
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
string connectionString = Environment.GetEnvironmentVariable("ConnectionString");
builder.Services.AddScoped(_ => new DataContext(new DbContextOptionsBuilder<DataContext>().Options, connectionString));
}
}
}

Summary

In the API class, we are injecting the data context into the class and using it in two data access methods. Those two functions will work as REST endpoints.

You can find the sample code here

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.