Esses dias me deparei com a tarefa de implementar o Entity Framework Core em uma Azure Function. Me deparei com uma situação na qual vale a pena ser compartilhada.

Sobre a function e o ambiente de desenvolvimento, temos a seguintes características:

Antes de seguir, caso seja a sua primeira vez lendo sobre Azure Functions, recomendo a leitura da documentação oficial. Além disso, eu já escrevi alguns posts sobre esse recurso de nuvem, você pode conferir aqui e aqui.

Inicialmente vamos criar o projeto de functions diretamente pela IDE.

A function criada será do tipo Timer trigger.

Para implementar o Entity Framework Core, iremos adicionar os seguintes pacotes ao nosso projeto. Vale mencionar que estamos criando um banco de dados do zero (SQL Server), por isso utilizaremos a abordagem code-first.

Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Design

No arquivo local.settings.json, vamos adicionar a connectionString do nosso banco de dados. Nesse caso, eu levantei um SQL Server via docker no meu ambiente local.

{
    "IsEncrypted": false,
    "Values": {
      "AzureWebJobsStorage": "UseDevelopmentStorage=true",
      "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
      "ConnectionString": "YOUR-CONNECTION-STRING"
  }
}

É hora de criar um modelo simples de dados. Vamos declarar a classe Book com apenas duas propriedades.

using System;
namespace AzureFunctionEF.Models
{
	public class Book
	{
		public Guid Id { get; set; }

		public string Name { get; set; } = default!;
    }
}

Devemos também implementar a classe Context. Observe que fizemos o mapeamento de como a classe Book será utilizada como base da tabela Books

using System;
using AzureFunctionEF.Models;
using Microsoft.EntityFrameworkCore;

namespace AzureFunctionEF.Data
{
	public class Context : DbContext
	{
        public Context(DbContextOptions options) : base(options)
        {
        }

        public DbSet<Book> Books { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Book>()
                .ToTable("Books");
        }
    }
}

Com a classe Context implementada, já podemos fazer a configuração do Entity Framework Core na classe Program.cs.

using AzureFunctionEF.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults();

builder.ConfigureServices((hostContext, services) =>
{
    string connectionString = hostContext.Configuration.GetSection("ConnectionString").Value!;

    services.AddDbContext<Context>(
        options => SqlServerDbContextOptionsExtensions.UseSqlServer(options, connectionString));
});

builder.Build().Run();

Para finalizar, basta fazermos a injeção de dependência do Context na function criada por default no nosso projeto. Essa function irá basicamente criar um novo registro de Book a cada período determinado pelo trigger da function.

using System;
using AzureFunctionEF.Data;
using AzureFunctionEF.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace AzureFunctionEF
{
    public class DefaultFunction
    {
        private readonly ILogger _logger;
        private readonly Context _context;

        public DefaultFunction(ILoggerFactory loggerFactory, Context context)
        {
            _logger = loggerFactory.CreateLogger<DefaultFunction>();
            _context = context;
        }

        [Function("DefaultFunction")]
        public async Task RunAsync([TimerTrigger("0 */1 * * * *")] MyInfo myTimer)
        {
            _logger.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");

            var id = Guid.NewGuid();

            await _context.Books.AddAsync(new Book
            {
                Id = id,    
                Name = $"Book_{id}"
            });

            await _context.SaveChangesAsync();

            _logger.LogInformation($"Next timer schedule at: {myTimer.ScheduleStatus.Next}");
        }
    }

    public class MyInfo
    {
        public MyScheduleStatus ScheduleStatus { get; set; }

        public bool IsPastDue { get; set; }
    }

    public class MyScheduleStatus
    {
        public DateTime Last { get; set; }

        public DateTime Next { get; set; }

        public DateTime LastUpdated { get; set; }
    }
}

Dito isso, já podemos gerar a primeira migration e fazer o update do banco de dados.

dotnet ef migrations add InitialMigration --verbose

Observe que recebemos um erro, dizendo que a classe Context não pode ser criada.

Microsoft.EntityFrameworkCore.Design.OperationException: Unable to create an object of type 'Context'

Para resolver esse problema, devemos criar a classe ContextFactory que implementa IDesignTimeDbContextFactory. Com isso podemos construir classes que herdam de DbContext em design-time. Recomendo a leitura deste conceito!

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;

namespace AzureFunctionEF.Data
{
	public class ContextFactory : IDesignTimeDbContextFactory<Context>
    {
        public Context CreateDbContext(string[] args)
        {

            var index = Array.IndexOf(args, "--connection-string");

            if (index == -1)
                throw new Exception("Parameter --connection-string did not provide");

            string connectionString = args[index + 1];

            Console.WriteLine(connectionString);

            var optionsBuilder = new DbContextOptionsBuilder<Context>();
            optionsBuilder.UseSqlServer(connectionString);

            return new Context(optionsBuilder.Options);
        }
    }
}

Dessa forma, já podemos criar a migration. Precisaremos desta vez passar a connection string via parâmetro.

dotnet ef migrations add InitialMigration --verbose -- --connection-string '<YOUR-CONNECTION-STRING>'

É hora de aplicarmos essa migration ao banco de dados.

dotnet ef database update --verbose -- --connection-string '<YOUR-CONNECTION-STRING>'

Podemos observar que tudo foi criado corretamente.

Ao executarmos a function, depois de alguns ciclos, percebemos ela está salvando novos registros de forma correta. O Entity Framework Core foi implementado corretamente!

Você já pode baixar o projeto por esse link, e não esquece de me seguir no LinkedIn!

Até a próxima, abraços!

💡
Podemos te ajudar com uma revisão 100% gratuita do seu ambiente cloud.
Share this post