DEV Community

Cover image for Using Entity Framework with Azure Functions
Jeff Hollan for Microsoft Azure

Posted on • Edited on

Using Entity Framework with Azure Functions

<< Update: The instructions below are still good but I did update my sample to use .NET Core 3.1 here if interested: https://github.com/jeffhollan/functions-chsarp-entitycore-3 >>

One of the exciting developments from Build this year was support for dependency injection in Azure Functions. This means you can register and use your own services as part of functions. While you've been able to use Entity Framework Core in the past, the pairing with dependency injection makes it a much more natural fit. Let's create a simple Azure Function that can interact with stateful data using Entity Framework Core. I'm going to create a very simple API that can get and set blog data in an Azure SQL Database.

Adding entity framework to a function project

First up, I created a brand new v2 (.NET Core 2) Azure Functions project. I selected the HTTP Trigger template to start with. In order to use dependency injection I need to verify two things:

  1. The Microsoft.NET.Sdk.Functions is at least 1.0.28
  2. Install the Microsoft.Azure.Functions.Extensions package to provide the API to use dependency injection
Update-Package Microsoft.NET.Sdk.Functions
Install-Package Microsoft.Azure.Functions.Extensions
Enter fullscreen mode Exit fullscreen mode

While we're at it, let's install the Entity Framework libraries. ⚠️ These versions are very particular with the version of .NET you will be running. So choose your version of these libraries thoughtfully. At the time of writing this Azure is running .NET Core 2.2.3 so that's what I'll use.

Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 2.2.3
Enter fullscreen mode Exit fullscreen mode

I also want to take advantage of the design-time migration, so I'll install the tools needed to drive that.

Install-Package Microsoft.EntityFrameworkCore.Design -Version 2.2.3
Install-Package Microsoft.EntityFrameworkCore.Tools -Version 2.2.3
Enter fullscreen mode Exit fullscreen mode

Add the entity models

Next, I'll create a very simple DbContext for the data models I want to interact with. Let's just stick with the simple Blog and Posts entities.

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;

namespace functions_csharp_entityframeworkcore
{
    public class BloggingContext : DbContext
    {
        public BloggingContext(DbContextOptions<BloggingContext> options)
            : base(options)
        { }

        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }
    }

    public class Blog
    {
        public int BlogId { get; set; }
        public string Url { get; set; }

        public ICollection<Post> Posts { get; set; }
    }

    public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public int BlogId { get; set; }
        public Blog Blog { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Write the function code to inject the entity context

I need to verify that I'm using constructors in my function classes so I can inject in the right dependencies (in this case, the entity framework context). This means getting rid of static and adding a constructor. I'll also modify the constructor to accept a BloggingContext created above (which itself will need a DbContextOptions<BloggingContext> injected).

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace functions_csharp_entityframeworkcore
{
    public class HttpTrigger
    {
        private readonly BloggingContext _context;
        public HttpTrigger(BloggingContext context)
        {
            _context = context;
        }

        [FunctionName("GetPosts")]
        public async Task<IActionResult> Get(
            [HttpTrigger(AuthorizationLevel.Function, "get", Route = "posts")] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            // Code that returns posts - you can see this code by going
            // to the full sample linked at the bottom
            return new OkResult();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

To register services like BloggingContext, I create a Startup.cs file in the project and implement the FunctionsStartup configuration. Notice I use an attribute to signal to the functions host where it can run my configuration.

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

[assembly: FunctionsStartup(typeof(functions_csharp_entityframeworkcore.Startup))]

namespace functions_csharp_entityframeworkcore
{
    class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            string SqlConnection = Environment.GetEnvironmentVariable("SqlConnectionString");
            builder.Services.AddDbContext<BloggingContext>(
                options => options.UseSqlServer(SqlConnection));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Enable design-time DbContext creation

As mentioned, I want to use the design-time DbContext creation. Since I'm not using ASP.NET directly here, but implementing the Azure Functions Configure method, entity framework won't automatically discover the desired DbContext. So I'll make sure to implement an IDesignTimeDbContextFactory to drive the tooling.

public class BloggingContextFactory : IDesignTimeDbContextFactory<BloggingContext>
{
    public BloggingContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
        optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("SqlConnectionString"));

        return new BloggingContext(optionsBuilder.Options);
    }
}
Enter fullscreen mode Exit fullscreen mode

Getting the project .dll in the right spot

There's one last thing I need to do. When you build an Azure Functions project, Microsoft.NET.Sdk.Functions does some organizing of the build artifacts to create a valid function project structure. One of the .dlls it moves to a sub-folder is the project .dll. Unfortunately, for the design-time tools like entity framework migration, it expects the .dll to be at the root of the build target. So to make sure both tools are happy, I'll add a quick post-build event to copy the .dll to the root as well. I added this to my .csproj file for the project.

<Target Name="PostBuild" AfterTargets="PostBuildEvent">
  <Exec Command="copy /Y &quot;$(TargetDir)bin\$(ProjectName).dll&quot; &quot;$(TargetDir)$(ProjectName).dll&quot;" />
</Target>
Enter fullscreen mode Exit fullscreen mode

Adding an entity framework migration

I went into the Azure Portal and created a simple Azure SQL database. I copied the connection string into the local.settings.json file with a new value for SqlConnectionString. You can see in my previous code samples I used that as the environment variable that would have the connection string. This way I don't have to check it into source control 🥽.

To make sure the connection string is also available to the CLI tooling for migrations, I'll open my Package Manager Console and set the environment variable before adding a migration and updating the database.

$env:SqlConnectionString="Server=tcp:mySqlServerStuffxxx"
Add-Migration InitialCreate
Update-Database
Enter fullscreen mode Exit fullscreen mode

When browsing to the SQL database in the Azure Portal, I can see the tables were successfully created!

Post create tables create

Writing the function app and publishing

That's all the heavy lifting. Now I just write a few simple HTTP triggered functions that can return and add data to the BloggerContext I injected in. When running the app, I can then call the different GET and PUT methods to validate data is being persisted and returned correctly.

After publishing, I just make sure to set the appropriate application setting for SqlConnectionString with my production SQL connection string, and we're in business!

You can see the full project code on my GitHub account

Top comments (18)

Collapse
 
chamikasandamal profile image
Chamika Sandamal Somasiri

Use Copy task instead of following. that will work on cross platform

<Exec Command="copy /Y &quot;$(TargetDir)bin\$(ProjectName).dll&quot; &quot;$(TargetDir)$(ProjectName).dll&quot;" />

docs.microsoft.com/en-us/visualstu...

Collapse
 
justrhysism profile image
Rhys Lloyd

Rather than using a Windows-only post build command, a MSBuild copy task might be a better option to support cross-platform development.

github.com/dotnet/core/issues/2065...

Collapse
 
djaus2 profile image
David Jones • Edited

Worth mentioning here: The BloggingContextFactory class code needs the following usings:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
Enter fullscreen mode Exit fullscreen mode

Also the line of code:

[assembly: FunctionsStartup(typeof(<ProjectName>.Startup))]
Enter fullscreen mode Exit fullscreen mode

that is required in the startup code page just before the namespace declaration is also worth mentioning at this level.

Also the

<Target Name="PostBuild"

bit IS now not needed.

Collapse
 
fosskolkata profile image
Dipendu Paul

Hi @Jeff et al.
I followed the blog and set up my EF migrations successfully. One issue that I found was that I broke down my application into multiple projects like Data, Core, Function App projects. So I had to use this script to copy all DLLs:

Collapse
 
xinhaiwang profile image
Xinhai Wang • Edited

Hello Jeff, Great news for dependency injection support in Azure Functions.

I am new to Azure. I have two questions about Azure Functions.

Question 1. Which equivalent aws database(dynamo and Aurora Serverless) I could use in Azure.

Question 2. Is there any cold-start problems for Azure Functions c#? I know there are java cold start problems in aws.

Collapse
 
djaus2 profile image
David Jones

I've deployed my function to Azure by publishing from Visual Studio.
I now get the message:
Your app is currently in read only mode because you are running from a package file. To make any changes update the content in your zip file and WEBSITE_RUN_FROM_PACKAGE app setting.

Any ideas on this? Most discussions say to delete that setting and and republish, but it reappears. Also if you set it to 0 (from1) it is reset to 1 after a republish.

PS A great article.

Collapse
 
prebenatp profile image
PrebenATP

Hmm. When I use the installed project template: Azure function it adds the SDK
Microsoft.NETCore.App (2.1)

Now following your guide I get this error:
"Package Microsoft.EntityFrameworkCore.SqlServer 3.0.1 is not compatible with netcoreapp2.1 (.NETCoreApp,Version=v2.1) ... supports: netstandard2.1 (.NETStandard,Version=v2.1)"

Same thing if I lower the version number to ...SqlServer 2.2.3

How do you got this to work I have no idea?
Any suggestions would make me happy :-)

Collapse
 
fosskolkata profile image
Dipendu Paul

It's late I know, but somebody coming across this issue : you need to downgrade Microsoft.EntityFrameworkCore.SqlServer version to match that of Microsoft.NETCore.App

Collapse
 
fosskolkata profile image
Dipendu Paul
Collapse
 
emilpeder profile image
emilpeder

Hi Jeff
Thanks for a great post.
Using Microsoft.NET.Sdk.Functions version 3.0.9 the dll copy step must be omitted. It seems the build itself then takes care of placing the .dlls correctly.

Collapse
 
millcraftmatt profile image
millcraftmatt

Thank you for this!

Collapse
 
ianrathbone profile image
Ian Rathbone

Hey Jeff this article is ace thank you!

The MS article you linked to about DI states you need at least v1.0.28 of Microsoft.NET.Sdk.Functions will that cause issues with entity framework?

Collapse
 
jeffhollan profile image
Jeff Hollan

No it won't - that's actually the version I'm using. I think I mistyped 1.0.27. 28 and beyond should be great.

Collapse
 
ianrathbone profile image
Ian Rathbone

Cool thank you!