O Entity Framework Core é um framework ORM (Object-Relational Mapper) que facilita o acesso e a manipulação de dados em bancos de dados relacionais a partir de código .NET. Uma das características poderosas do EF Core é sua capacidade de mapear entidades de banco de dados para classes .NET e vice-versa.

Neste artigo, vamos explorar a integração das views do banco de dados diretamente no código-fonte das nossas aplicações, utilizando as migrações. Essa abordagem pode ser uma estratégia eficaz para evitar a necessidade de versionar as views separadamente do código-fonte, simplificando assim o processo de desenvolvimento e manutenção.

Vamos criar inicialmente um diretório para o nosso projeto:

mkdir EfCoreViews

Como iremos fazer uma integração com o EF Core, subiremos um banco de dados por meio de um docker compose.

version: '3.8'
services:
  database:
    container_name: "sqlserver"
    hostname: "sqlserver"
    ports:
      - "1433:1433"
    image: "mcr.microsoft.com/azure-sql-edge:latest"
    environment:
      ACCEPT_EULA: "1"
      MSSQL_SA_PASSWORD: "Teste12345!"
    

Vamos ainda criar o versionamento via git no diretório raiz.

dotnet new gitignore
git init

Com as dependências prontas, já podemos criar o projeto do tipo console e adicionar alguns pacotes NuGet a esse projeto criado.

dotnet new console
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design

A ideia desse post é criar um pequeno projeto que utiliza views para simplificar as queries. Por isso, vamos criar uma modelagem simples de dados: Iremos ter uma entidade chamada Author que possui uma lista de Books:

namespace EfCoreViews.Models;

public class Author
{
    public Guid Id { get; set; }
    public string Name { get; set; } = default!;
    public IList<Book> Books { get; set; } = new List<Book>();
}
namespace EfCoreViews.Models;

public class Book
{
    public Guid Id { get; set; }
    public string Name { get; set; } = default!;
    public Guid AuthorId { get; set; } = default!;
    public Author Author { get; set; } = default!;
}

Com as entidades prontas, podemos criar suas configurações para o code-first:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace EfCoreViews.Models.Configurations;

public class AuthorConfiguration :  IEntityTypeConfiguration<Author>
{
    public void Configure(EntityTypeBuilder<Author> builder)
    {
        builder.HasKey(e => e.Id);
        
        builder.Property(e => e.Name)
            .IsRequired();

        builder.HasData(new List<Author>
        {
            new()
            {
                Id = Guid.Parse("92338F02-F3A5-4C82-AC9F-F9DC1A8D7256"),
                Name = "J. R. R. Tolkien"
            },
            new()
            {
                Id = Guid.Parse("D7890984-FC4E-4AFA-8FC4-530F4C684A26"),
                Name = "James Carroll"
            }
        });
    }
}
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace EfCoreViews.Models.Configurations;

public class BookConfiguration :  IEntityTypeConfiguration<Book>
{
    public void Configure(EntityTypeBuilder<Book> builder)
    {
        builder.HasKey(e => e.Id);
        
        builder.Property(e => e.Name)
            .IsRequired();

        builder.HasOne(x => x.Author)
            .WithMany(x => x.Books)
            .HasForeignKey(x => x.AuthorId);
        
        builder.HasData(new List<Book>
        {
            new()
            {
                Id = Guid.Parse("2AE6873C-31BC-48DE-85F9-C63289A312B3"),
                AuthorId = Guid.Parse("92338F02-F3A5-4C82-AC9F-F9DC1A8D7256"),
                Name = "The lord of the rings"
            },
            new()
            {
                Id = Guid.Parse("72C6EC9B-3964-4272-BED5-4D460020B79B"),
                AuthorId = Guid.Parse("92338F02-F3A5-4C82-AC9F-F9DC1A8D7256"),
                Name = "The hobbit"
            }
        });
    }
}

Apenas um detalhe. Fizemos o seed de alguns dados!

Para finalizar as configurações do EF Core, precisamos criar a classe principal dessa ferramenta, o AppDbContext que herda de DbContext:

using EfCoreViews.Models;
using Microsoft.EntityFrameworkCore;

namespace EfCoreViews;

public class AppDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer(@"Server=localhost;Database=MyDb;User Id=Sa;Password=Teste12345!;TrustServerCertificate=true");
    }
    
     public DbSet<Book> Books { get; set; } = null!;
     public DbSet<Author> Authors { get; set; } = null!;
    

     protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
    }
}

Vamos criar a migration e aplicá-la ao banco de dados.

dotnet ef migrations add InitialCreate
dotnet ef database update 

Com os comandos finalizados, podemos ver que as tabelas foram criadas!

Obs: Estou utilizando o Azure Data Studio!

Na classe Program.cs, podemos criar um pequeno código para fazer uma query que utiliza algumas manipulações extras de dados (Include e Select).

// See https://aka.ms/new-console-template for more information

using EfCoreViews;
using Microsoft.EntityFrameworkCore;

await using var context = new AppDbContext();

var results = await context.Authors
    .Include(x => x.Books)
    .Select(x => new
    {
        Author = x,
        BookCount = x.Books.Count
    })
    .ToListAsync();

foreach (var result in results)
{
    Console.WriteLine($"Author: {result.Author.Name} -> Book count: {result.BookCount}");
}

Temos o resultado:

A ideia agora é criar uma view que realiza essa mesma query. Para que essa view seja versionada juntamente com o código-fonte, vamos criar uma migration vazia por meio do seguinte comando:

 dotnet ef migrations add AddView
 

Vamos aprimorar seu conteúdo com o seguinte texto:

using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace EfCoreViews.Migrations
{
    /// <inheritdoc />
    public partial class AddView : Migration
    {
        /// <inheritdoc />
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql(@"
            CREATE VIEW 
                AuthorBookCounting
            AS
            SELECT 
                a.Name AS Author,
                COUNT(b.Id) AS BookCount
            FROM 
                Authors a
            LEFT JOIN
                Books b ON b.AuthorId = a.Id
            GROUP BY 
                a.Name;
                ");
        }

        /// <inheritdoc />
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql("DROP VIEW AuthorBookCounting");
        }
    }
}

Observe que no método Up, criamos a view. E no método Down, a deletamos.

Para servir como retorno da view, vamos criar um modelo chamado AuthorBookCount.

namespace EfCoreViews.Models;

public class AuthorBookCount
{
    public Guid Id { get; set; }
    public string Author { get; set; } = default!;
    public int BookCount { get; set; } = default!;
}

el

Precisamos ainda acionar um novo DbSet ao AppDbContex, e a configuração (code-first) do modelo AuthorBookCount.

public DbSet<AuthorBookCount> AuthorBookCount { get; set; } = null!;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace EfCoreViews.Models.Configurations;

public class AuthorBookCountConfigurationConfiguration :  IEntityTypeConfiguration<AuthorBookCount>
{
    public void Configure(EntityTypeBuilder<AuthorBookCount> builder)
    {
        builder.HasKey(e => e.Id);
        builder.ToView("AuthorBookCounting");
    }
}

Com essas modificações prontas, vamos modificar o código do Program.cs:

using EfCoreViews;
using Microsoft.EntityFrameworkCore;

await using var context = new AppDbContext();

var results = await context.AuthorBookCount.ToListAsync();

foreach (var result in results)
{
    Console.WriteLine($"Author: {result.Author} -> Book count: {result.BookCount}");
}

Observe que realizamos a query por meio da view AuthorBookCount!

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