Tutorial: Introdução ao EF Core em um aplicativo Web ASP.NET MVC

Por Tom Dykstra e Rick Anderson

Este tutorial ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e exibições. Razor Pages é um modelo de programação alternativo. Para novos desenvolvimentos, recomendamos o Razor Pages em vez do MVC com controladores e exibições. Consulte a versão do Razor Pages deste tutorial. Cada tutorial aborda um material diferente:

Este tutorial do MVC traz algumas informações que o do Razor Pages não traz:

  • Implementar a herança no modelo de dados
  • Executar consultas SQL brutas
  • Usar o LINQ dinâmico para simplificar o código

Informações que o tutorial do Razor Pages traz que este não traz:

  • Usar o método Select para carregar dados relacionados
  • Melhores práticas para o EF.

O aplicativo Web de amostra da Contoso University demonstra como criar um aplicativo Web ASP.NET Core 2.0 MVC usando o Entity Framework (EF) Core e o Visual Studio.

O aplicativo de exemplo é um site de uma Contoso University fictícia. Ele inclui funcionalidades como admissão de alunos, criação de cursos e atribuições de instrutor. Essa é a primeira de uma série de tutoriais que explica como criar o aplicativo de exemplo Contoso University.

Pré-requisitos

Esse tutorial não foi atualizado para ASP.NET Core 6 ou posterior. As instruções do tutorial não funcionarão corretamente se você criar um projeto direcionado ao ASP.NET Core 6 ou posterior. Por exemplo, os modelos da Web do ASP.NET Core 6 e posterior usam o modelo de hospedagem mínima, que unifica Startup.cs e Program.cs em um só arquivo Program.cs.

Outra diferença introduzida no .NET 6 é o recurso NRT (tipos de referência anuláveis). Os modelos de projeto habilitam esse recurso por padrão. Podem ocorrer problemas em que o EF considera uma propriedade a ser necessária no .NET 6 que é anulável no .NET 5. Por exemplo, a página Criar Aluno falhará silenciosamente, a menos que a propriedade Enrollments seja tornada anulável ou a marcação do auxiliar asp-validation-summary seja alterada de ModelOnly para All.

Recomendamos que você instale e use o SDK do .NET 5 para este tutorial. Até que este tutorial seja atualizado, consulte Páginas do Razor com o Entity Framework Core no ASP.NET Core - Tutorial 1 de 8 sobre como usar o Entity Framework com ASP.NET Core 6 ou posterior.

Mecanismos de banco de dados

As instruções do Visual Studio usam SQL Server LocalDB, uma versão do SQL Server Express que é executada somente no Windows.

Resolver problemas e solucionar problemas

Caso tenha um problema que não consiga resolver, em geral, você poderá encontrar a solução comparando o código com o projeto concluído. Para obter uma lista de erros comuns e como resolvê-los, consulte a seção Solução de problemas do último tutorial da série. Caso não encontre o que precisa na seção, publique uma pergunta no StackOverflow.com sobre o ASP.NET Core ou o EF Core.

Dica

Esta é uma série de dez tutoriais, cada um se baseando no que é feito nos tutoriais anteriores. Considere a possibilidade de salvar uma cópia do projeto após a conclusão bem-sucedida de cada tutorial. Caso tenha problemas, comece novamente no tutorial anterior em vez de voltar ao início de toda a série.

Aplicativo Web Contoso University

O aplicativo criado nesses tutoriais é um site básico de universidade.

Os usuários podem exibir e atualizar informações de alunos, cursos e instrutores. Estas são algumas das telas no aplicativo:

Students Index page

Students Edit page

Criar um aplicativo Web

  1. Inicie o Visual Studio e selecione Criar um projeto.
  2. Na caixa de diálogo Criar um novo projeto, selecione Aplicativo Web ASP.NET Core>Avançar.
  3. No diálogo Configurar seu novo projeto, insira ContosoUniversity como Nome do projeto. É importante usar esse nome exato, incluindo a capitalização, para que cada correspondência namespace quando o código é copiado.
  4. Selecione Criar.
  5. Na caixa de diálogo Criar um novo Aplicativo Web ASP.NET Core, selecione:
    1. .NET Core e ASP.NET Core 5.0 nas listas suspensas.
    2. Aplicativo Web ASP.NET Core (Modelo-Exibição-Controlador).
    3. CriarNew ASP.NET Core Project dialog

Configurar o estilo do site

Algumas alterações básicas configuram o menu do site, o layout e a home page.

Abra Views/Shared/_Layout.cshtml e faça as seguintes alterações:

  • Altere cada ocorrência de ContosoUniversity para Contoso University. Há três ocorrências.
  • Adicione entradas de menu Sobre, Alunos, Cursos, Instrutores e Departamentos e exclua a entrada de menu Privacy.

As alterações anteriores são realçadas no seguinte código:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Courses" asp-action="Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Departments" asp-action="Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2020 - Contoso University - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Em Views/Home/Index.cshtml, substitua o conteúdo do arquivo pela seguinte marcação:

@{
    ViewData["Title"] = "Home Page";
}

<div class="jumbotron">
    <h1>Contoso University</h1>
</div>
<div class="row">
    <div class="col-md-4">
        <h2>Welcome to Contoso University</h2>
        <p>
            Contoso University is a sample application that
            demonstrates how to use Entity Framework Core in an
            ASP.NET Core MVC web application.
        </p>
    </div>
    <div class="col-md-4">
        <h2>Build it from scratch</h2>
        <p>You can build the application by following the steps in a series of tutorials.</p>
        <p><a class="btn btn-default" href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the tutorial &raquo;</a></p>
    </div>
    <div class="col-md-4">
        <h2>Download it</h2>
        <p>You can download the completed project from GitHub.</p>
        <p><a class="btn btn-default" href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-mvc/intro/samples/5cu-final">See project source code &raquo;</a></p>
    </div>
</div>

Pressione CTRL+F5 para executar o projeto ou escolha Depurar> Iniciar sem Depuração no menu. A home page é exibida com guias para as páginas criadas neste tutorial.

Contoso University home page

Pacotes EF Core NuGet

Este tutorial usa o SQL Server e o pacote de provedor é Microsoft.EntityFrameworkCore.SqlServer.

O pacote SQL Server do EF e suas dependências, Microsoft.EntityFrameworkCore e Microsoft.EntityFrameworkCore.Relational, fornecem suporte de runtime para o EF.

Adicione o pacote NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore. No Console do gerenciador de pacotes (PMC, na sigla em inglês), execute os seguintes comandos para adicionar os pacotes do NuGet:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer

O pacote Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore NuGet fornece middleware do ASP.NET Core para páginas de erro EF Core. Esse middleware ajuda a detectar e diagnosticar erros com migrações EF Core.

Para obter informações sobre outros provedores de banco de dados que estão disponíveis para o EF Core, consulte Provedores de banco de dados.

Criar o modelo de dados

As seguintes classes de entidade são criadas para este aplicativo:

Course-Enrollment-Student data model diagram

As entidades anteriores têm as seguintes relações:

  • Há uma relação de um-para-muitos entre as entidades Student e Enrollment. Um aluno pode se registrar em qualquer quantidade de cursos.
  • Há uma relação de um-para-muitos entre as entidades Course e Enrollment. Um curso pode ter qualquer quantidade de alunos registrados.

Nas seções a seguir, é criada uma classe para cada uma dessas entidades.

A entidade Student

Student entity diagram

Na pasta Modelos, crie a classe Student com o seguinte código:

using System;
using System.Collections.Generic;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

A propriedade ID se torna a coluna de chave primária (PK) da tabela de banco de dados que corresponde a essa classe. Por padrão, o EF interpreta uma propriedade chamada ID ou classnameID como a chave primária. Por exemplo, o PK pode ser chamado StudentID em vez de ID.

A propriedade Enrollments é uma propriedade de navegação. As propriedades de navegação armazenam outras entidades que estão relacionadas a essa entidade. A propriedade Enrollments de uma entidade Student:

  • Contém todas as entidades Enrollment relacionadas a essa entidade Student.
  • Se uma linha específica Student no banco de dados tiver duas linhas relacionadas Enrollment:
    • A Student propriedade de navegação dessa Enrollments entidade contém essas duas entidades Enrollment.

As linhas Enrollment contêm o valor PK de um aluno na coluna da chave estrangeira StudentID (FK).

Se uma propriedade de navegação consegue armazenar várias entidades:

  • O tipo deve ser uma lista, como ICollection<T>, List<T>ou HashSet<T>.
  • As entidades podem ser adicionadas, excluídas e atualizadas.

Relações de navegação muitos para muitos e um para muitos podem conter várias entidades. Quando ICollection<T> é usado, o EF cria uma coleção HashSet<T> por padrão.

A entidade Enrollment

Enrollment entity diagram

Na pasta Modelos, crie a classe Enrollment com o seguinte código:

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

A propriedade EnrollmentID é a PK. Essa entidade usa o padrão classnameID em vez de ID por conta própria. A entidade Student usou o padrão ID. Alguns desenvolvedores preferem usar um padrão em todo o modelo de dados. Neste tutorial, a variação ilustra que qualquer padrão pode ser usado. Um tutorial posterior mostra como o uso de ID sem nome de classe facilita a implementação da herança no modelo de dados.

A propriedade Grade é um enum. O ? após a declaração de tipo Grade indica que a propriedade Grade é anulável. Uma nota que seja null é diferente de uma nota zero. null significa que uma nota não é conhecida ou ainda não foi atribuída.

A propriedade StudentID é uma chave estrangeira (FK) e a propriedade de navegação correspondente é Student. Uma entidade Enrollment está associada a uma entidade Student e, portanto, a propriedade só pode manter uma única entidade Student. Isso difere da propriedade de navegação Student.Enrollments, que pode manter várias entidades Enrollment.

A propriedade CourseID é uma FK e a propriedade de navegação correspondente é Course. Uma entidade Enrollment está associada a uma entidade Course.

O Entity Framework interpretará uma propriedade como uma propriedade FK se ela for chamada <nome da propriedade de navegação><nome da propriedade da chave primária>. Por exemplo, StudentID para a propriedade de navegação Student, pois a PK da entidade Student é ID. As propriedades FK também podem ser chamadas <nome da propriedade da chave primária>. Por exemplo, CourseID porque o PK da entidade Course é CourseID.

A entidade Course

Course entity diagram

Na pasta Modelos, crie a classe Course com o seguinte código:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

A propriedade Enrollments é uma propriedade de navegação. Uma entidade Course pode estar relacionada a qualquer quantidade de entidades Enrollment.

O atributo DatabaseGenerated é explicado em um tutorial posterior. Esse atributo permite inserir o PK para o curso em vez de fazer com que o banco de dados o gere.

Criar o contexto de banco de dados

A classe principal que coordena a funcionalidade do EF de um determinado modelo de dados é a classe de contexto de banco de dados DbContext. Essa classe é criada derivando-a da classe Microsoft.EntityFrameworkCore.DbContext. A classe derivada DbContext especifica quais entidades são incluídas no modelo de dados. Alguns comportamentos do EF podem ser personalizados. Neste projeto, a classe é chamada SchoolContext.

Na pasta do projeto, crie uma pasta chamada Data.

Na pasta Dados, crie uma classe SchoolContext com o seguinte código:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
        {
        }

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }
    }
}

O código anterior cria uma propriedade DbSet para cada conjunto de entidades. Na terminologia do EF:

  • Um conjunto de entidades normalmente corresponde a uma tabela de banco de dados.
  • Uma entidade corresponde a uma linha da tabela.

As instruções DbSet<Enrollment> e DbSet<Course> podem ser omitidas e elas funcionarão da mesma maneira. O EF as incluiria implicitamente porque:

  • A entidade Student faz referência à entidade Enrollment.
  • A entidade Enrollment faz referência à entidade Course.

Quando o banco de dados é criado, o EF cria tabelas que têm nomes iguais aos nomes de propriedade DbSet. Nomes de propriedade para coleções são normalmente plurais. Por exemplo, Students em vez de Student. Os desenvolvedores não concordam sobre se os nomes de tabela devem ser pluralizados ou não. Para esses tutoriais, o comportamento padrão é substituído pela especificação de nomes singulares de tabela no DbContext. Para fazer isso, adicione o código realçado a seguir após a última propriedade DbSet.

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
        {
        }

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

Registrar o SchoolContext

O ASP.NET Core inclui a injeção de dependência. Serviços, como o contexto de banco de dados do EF, são registrados com injeção de dependência durante a inicialização do aplicativo. Os componentes que exigem esses serviços, como controladores de MVC, recebem esses serviços por meio de parâmetros do construtor. O código de construtor do controlador que obtém uma instância de contexto é mostrado mais adiante neste tutorial.

Para registrar SchoolContext como um serviço, abra Startup.cs e adicione as linhas realçadas ao método ConfigureServices.

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ContosoUniversity
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<SchoolContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

            services.AddControllersWithViews();
        }

O nome da cadeia de conexão é passado para o contexto com a chamada de um método em um objeto DbContextOptionsBuilder. Para o desenvolvimento local, o sistema de configuração do ASP.NET Core lê a cadeia de conexão do arquivo appsettings.json.

Abra o arquivo appsettings.json e adicione uma cadeia de conexão, conforme mostrado na seguinte marcação:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Adicionar o filtro de exceção de banco de dados

Adicione AddDatabaseDeveloperPageExceptionFilter como ConfigureServices conforme mostrado no código a seguir:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<SchoolContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddDatabaseDeveloperPageExceptionFilter();

    services.AddControllersWithViews();
}

O AddDatabaseDeveloperPageExceptionFilter fornece informações de erro úteis no ambiente de desenvolvimento.

SQL Server Express LocalDB

A cadeia de conexão especifica um LocalDB do SQL Server. LocalDB é uma versão leve do Mecanismo de Banco de Dados do SQL Server Express destinado ao desenvolvimento de aplicativos, e não ao uso em produção. O LocalDB é iniciado sob demanda e executado no modo de usuário e, portanto, não há nenhuma configuração complexa. Por padrão, o LocalDB cria arquivos .mdf de BD no diretório C:/Users/<user>.

Inicializar o BD com os dados de teste

O EF cria um banco de dados vazio. Nesta seção, um método é adicionado, que é chamado depois que o banco de dados é criado para populá-lo com os dados de teste.

O método EnsureCreated é usado para criar automaticamente o banco de dados. Em um tutorial posterior, você verá como manipular as alterações do modelo usando as Migrações do Code First para alterar o esquema de banco de dados, em vez de remover e recriar o banco de dados.

Na pasta Dados, crie uma nova classe chamada DbInitializer com o seguinte código:

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
            new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
            new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
            new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
            };
            foreach (Student s in students)
            {
                context.Students.Add(s);
            }
            context.SaveChanges();

            var courses = new Course[]
            {
            new Course{CourseID=1050,Title="Chemistry",Credits=3},
            new Course{CourseID=4022,Title="Microeconomics",Credits=3},
            new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
            new Course{CourseID=1045,Title="Calculus",Credits=4},
            new Course{CourseID=3141,Title="Trigonometry",Credits=4},
            new Course{CourseID=2021,Title="Composition",Credits=3},
            new Course{CourseID=2042,Title="Literature",Credits=4}
            };
            foreach (Course c in courses)
            {
                context.Courses.Add(c);
            }
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
            new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
            new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
            new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
            new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
            new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
            new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
            new Enrollment{StudentID=3,CourseID=1050},
            new Enrollment{StudentID=4,CourseID=1050},
            new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
            new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
            new Enrollment{StudentID=6,CourseID=1045},
            new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };
            foreach (Enrollment e in enrollments)
            {
                context.Enrollments.Add(e);
            }
            context.SaveChanges();
        }
    }
}

O código anterior verifica se o banco de dados existe:

  • Se o banco de dados não for encontrado;
    • Ele é criado e carregado com dados de teste. Ele carrega os dados de teste em matrizes em vez de em coleções List<T> para otimizar o desempenho.
  • Se o banco de dados for encontrado, ele não executará nenhuma ação.

Atualize Program.cs com o seguinte código:

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Program.cs faz o seguinte na inicialização do aplicativo:

  • Obtenha uma instância de contexto de banco de dados do contêiner de injeção de dependência.
  • Chame o método DbInitializer.Initialize .
  • Descarta o contexto quando o método Initialize for concluído, conforme mostrado no código a seguir:
public static void Main(string[] args)
{
     var host = CreateWebHostBuilder(args).Build();

    using (var scope = host.Services.CreateScope())
    {
        var services = scope.ServiceProvider;
        try
        {
            var context = services.GetRequiredService<SchoolContext>();
            DbInitializer.Initialize(context);
        }
        catch (Exception ex)
        {
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogError(ex, "An error occurred while seeding the database.");
        }
    }

    host.Run();
}

A primeira vez que o aplicativo é executado, o banco de dados é criado e carregado com os dados de teste. Sempre que o modelo de dados é alterado:

  • Exclua o banco de dados.
  • Atualize o método de semente e comece de novo com um novo banco de dados.

Nos próximos tutoriais, o banco de dados é modificado quando o modelo de dados é alterado, sem excluir nem recriar o banco de dados. Nenhum dado é perdido quando o modelo de dados é alterado.

Criar um controlador e exibições

Use o mecanismo de scaffolding no Visual Studio para adicionar um controlador MVC e exibições que usam o EF para consultar e salvar dados.

A criação automática de métodos de ação CRUD e exibições são conhecidas como scaffolding.

  • No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Controllers e selecione Adicionar > Novo Item com Scaffold.
  • Na caixa de diálogo Adicionar Scaffolding:
    • Selecione Controlador MVC com exibições, usando o Entity Framework.
    • Clique em Adicionar. A caixa de diálogo Adicionar Controlador MVC com exibições, usando o Entity Framework é exibida: Scaffold Student
    • Na Classe de modelo, selecione Aluno.
    • Na Classe de contexto de dados, selecione SchoolContext.
    • Aceite o StudentsController padrão como o nome.
    • Clique em Adicionar.

O mecanismo de scaffolding do Visual Studio cria um arquivo StudentsController.cs e um conjunto de exibições (arquivos *.cshtml) que funcionam com o controlador.

Observe que o controlador usa um SchoolContext como parâmetro de construtor.

namespace ContosoUniversity.Controllers
{
    public class StudentsController : Controller
    {
        private readonly SchoolContext _context;

        public StudentsController(SchoolContext context)
        {
            _context = context;
        }

A injeção de dependência do ASP.NET Core é responsável por passar uma instância de SchoolContext para o controlador. Você configurou isso na classe Startup.

O controlador contém um método de ação Index, que exibe todos os alunos no banco de dados. O método obtém uma lista de alunos do conjunto de entidades Students pela leitura da propriedade Students da instância de contexto de banco de dados:

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}

Os elementos de programação assíncronos nesse código são explicados mais adiante no tutorial.

A exibição Views/Students/Index.cshtml exibe esta lista em uma tabela:

@model IEnumerable<ContosoUniversity.Models.Student>

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
                <th>
                    @Html.DisplayNameFor(model => model.LastName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstMidName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.EnrollmentDate)
                </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Pressione CTRL+F5 para executar o projeto ou escolha Depurar> Iniciar sem Depuração no menu.

Clique na guia Alunos para ver os dados de teste inserido pelo método DbInitializer.Initialize. Dependendo da largura da janela do navegador, você verá o link da guia Students na parte superior da página ou precisará clicar no ícone de navegação no canto superior direito para ver o link.

Contoso University home page narrow

Students Index page

Exibir o banco de dados

Quando o aplicativo é iniciado, o método DbInitializer.Initialize chama EnsureCreated. O EF viu que não havia banco de dados:

  • Por isso, ele criou um banco de dados.
  • O código do método Initialize preencheu o banco de dados com dados.

Use o Pesquisador de Objetos do SQL Server (SSOX) para exibir o banco de dados no Visual Studio:

  • Selecione Pesquisador de Objetos do SQL Server no menu Exibir do Visual Studio.
  • No SSOX, selecione (localdb)\MSSQLLocalDB >Bancos de Dados.
  • Selecione ContosoUniversity1, a entrada para o nome do banco de dados que está na cadeia de conexão no arquivo appsettings.json.
  • Expanda o nó Tabelas para ver as tabelas no banco de dados.

Tables in SSOX

Clique com o botão direito do mouse na tabela Aluno e clique em Exibir Dados para ver os dados na tabela.

Student table in SSOX

Os arquivos de banco de dados *.mdf e *.ldf estão na pasta C:\Users\<username>.

Como EnsureCreated é chamado no método inicializador que é executado na inicialização do aplicativo, você pode:

  • Fazer uma alteração na classe Student.
  • Exclua o banco de dados.
  • Parar e iniciar o aplicativo. O banco de dados é recriado automaticamente para corresponder à alteração.

Por exemplo, se uma propriedade EmailAddress é adicionada à classe Student, uma nova coluna EmailAddress na tabela recriada. O modo de exibição não exibirá a nova propriedade EmailAddress.

Convenções

A quantidade de código gravada para que o EF crie um banco de dados completo é mínima, devido ao uso de convenções pelo EF:

  • Os nomes de propriedades DbSet são usadas como nomes de tabela. Para entidades não referenciadas por uma propriedade DbSet, os nomes de classe de entidade são usados como nomes de tabela.
  • Os nomes de propriedade de entidade são usados para nomes de coluna.
  • As propriedades de entidade chamadas ID ou classnameID são reconhecidas como propriedades PK.
  • Uma propriedade será interpretada como uma propriedade FK se for chamada <nome da propriedade de navegação><nome da propriedade PK>. Por exemplo, StudentID para a propriedade de navegação Student, pois a PK da entidade Student é ID. As propriedades FK também podem ser chamadas <nome da propriedade da chave primária>. Por exemplo, EnrollmentID pois a PK da entidade Enrollment é EnrollmentID.

O comportamento convencional pode ser substituído. Por exemplo, os nomes de tabela podem ser especificados de forma explícita, conforme mostrado anteriormente neste tutorial. Os nomes de coluna e qualquer propriedade podem ser definidos como uma PK ou FK.

Código assíncrono

A programação assíncrona é o modo padrão do ASP.NET Core e do EF Core.

Um servidor Web tem um número limitado de threads disponíveis e, em situações de alta carga, todos os threads disponíveis podem estar em uso. Quando isso acontece, o servidor não pode processar novas solicitações até que os threads são liberados. Com um código síncrono, muitos threads podem ser vinculados enquanto realmente não são fazendo nenhum trabalho porque estão aguardando a conclusão da E/S. Com um código assíncrono, quando um processo está aguardando a conclusão da E/S, seu thread é liberado para o servidor para ser usado para processar outras solicitações. Como resultado, o código assíncrono permite que os recursos do servidor sejam usados com mais eficiência, e o servidor fica capacitado a manipular mais tráfego sem atrasos.

O código assíncrono introduz uma pequena quantidade de sobrecarga em tempo de execução, mas para situações de baixo tráfego, o impacto no desempenho é insignificante, ao passo que, em situações de alto tráfego, a melhoria de desempenho potencial é significativa.

No código a seguir, async, Task<T>, await e ToListAsync fazem com que o código seja executado de forma assíncrona.

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}
  • A palavra-chave async instrui o compilador a gerar retornos de chamada para partes do corpo do método e a criar automaticamente o objeto Task<IActionResult> que é retornado.
  • O tipo de retorno Task<IActionResult> representa um trabalho em andamento com um resultado do tipo IActionResult.
  • A palavra-chave await faz com que o compilador divida o método em duas partes. A primeira parte termina com a operação que é iniciada de forma assíncrona. A segunda parte é colocada em um método de retorno de chamada que é chamado quando a operação é concluída.
  • ToListAsync é a versão assíncrona do método de extensão ToList.

Algumas coisas a serem consideradas ao gravar um código assíncrono que usa o EF:

  • Somente instruções que fazem com que consultas ou comandos sejam enviados ao banco de dados são executadas de forma assíncrona. Isso inclui, por exemplo, ToListAsync, SingleOrDefaultAsync e SaveChangesAsync. Isso não inclui, por exemplo, instruções que apenas alteram um IQueryable, como var students = context.Students.Where(s => s.LastName == "Davolio").
  • Um contexto do EF não é thread-safe: não tente realizar várias operações em paralelo. Quando você chamar qualquer método assíncrono do EF, sempre use a palavra-chave await.
  • Para aproveitar os benefícios de desempenho do código assíncrono, verifique se os pacotes de biblioteca usados também usam o código assíncrono se eles chamam métodos do EF que fazem com que consultas sejam enviadas ao banco de dados.

Para obter mais informações sobre a programação assíncrona no .NET, consulte Visão geral da programação assíncrona.

Limitar entidades buscadas

Consulte Considerações de desempenho para obter informações sobre como limitar o número de entidades retornadas de uma consulta.

Registro em log do SQL do Entity Framework Core

A configuração de log geralmente é fornecida pela seção Logging dos arquivos appsettings.{Environment}.json. Para registrar instruções SQL em log, adicione "Microsoft.EntityFrameworkCore.Database.Command": "Information" ao arquivo appsettings.Development.json:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDB-2;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
     ,"Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  },
  "AllowedHosts": "*"
}

Com o JSON anterior, as instruções SQL são exibidas na linha de comando e na janela de saída do Visual Studio.

Para obter mais informações, consulte Registrando em log no .NET Core e no ASP.NET Core e este problema do GitHub.

Vá para o próximo tutorial para aprender a executar operações CRUD (criar, ler, atualizar e excluir) básicas.

Este tutorial ensina a usar o ASP.NET Core MVC e o Entity Framework Core com controladores e exibições. Razor Pages é um modelo de programação alternativo. Para novos desenvolvimentos, recomendamos o Razor Pages em vez do MVC com controladores e exibições. Consulte a versão do Razor Pages deste tutorial. Cada tutorial aborda um material diferente:

Este tutorial do MVC traz algumas informações que o do Razor Pages não traz:

  • Implementar a herança no modelo de dados
  • Executar consultas SQL brutas
  • Usar o LINQ dinâmico para simplificar o código

Informações que o tutorial do Razor Pages traz que este não traz:

  • Usar o método Select para carregar dados relacionados
  • Melhores práticas para o EF.

O aplicativo Web de amostra da Contoso University demonstra como criar aplicativos Web ASP.NET Core 2.2 MVC usando o Entity Framework (EF) Core 2.2 e o Visual Studio 2019.

Este tutorial não foi atualizado para o ASP.NET Core 3.1. Ele foi atualizado para o ASP.NET Core 5.0.

O aplicativo de exemplo é um site de uma Contoso University fictícia. Ele inclui funcionalidades como admissão de alunos, criação de cursos e atribuições de instrutor. Este é o primeiro de uma série de tutoriais que explica como criar o aplicativo de exemplo Contoso University do zero.

Pré-requisitos

  • SDK do .NET Core 2.2
  • Visual Studio 2019 com as cargas de trabalho a seguir:
    • Carga de trabalho ASP.NET e desenvolvimento para a Web
    • Carga de trabalho de desenvolvimento multiplataforma do .NET Core

Solução de problemas

Caso tenha um problema que não consiga resolver, em geral, você poderá encontrar a solução comparando o código com o projeto concluído. Para obter uma lista de erros comuns e como resolvê-los, consulte a seção Solução de problemas do último tutorial da série. Caso não encontre o que precisa na seção, publique uma pergunta no StackOverflow.com sobre o ASP.NET Core ou o EF Core.

Dica

Esta é uma série de dez tutoriais, cada um se baseando no que é feito nos tutoriais anteriores. Considere a possibilidade de salvar uma cópia do projeto após a conclusão bem-sucedida de cada tutorial. Caso tenha problemas, comece novamente no tutorial anterior em vez de voltar ao início de toda a série.

Aplicativo Web Contoso University

O aplicativo que você criará nestes tutoriais é um site simples de uma universidade.

Os usuários podem exibir e atualizar informações de alunos, cursos e instrutores. Estas são algumas das telas que você criará.

Students Index page

Students Edit page

Criar um aplicativo Web

  • Abra o Visual Studio.

  • No menu Arquivo, selecione Novo > Projeto.

  • No painel esquerdo, selecione Instalado > Visual C# > Web.

  • Selecione o modelo de projeto Aplicativo Web ASP.NET Core.

  • Insira ContosoUniversity como o nome e clique em OK.

    New Project dialog

  • Aguarde a caixa de diálogo Novo Aplicativo Web ASP.NET Core aparecer.

  • Selecione .NET Core, ASP.NET Core 2.2 e o modelo Aplicativo Web (Model-View-Controller).

  • Verifique se a Autenticação está definida como Sem Autenticação.

  • Selecione OK

    New ASP.NET Core Project dialog

Configurar o estilo do site

Algumas alterações simples configurarão o menu do site, o layout e a home page.

Abra Views/Shared/_Layout.cshtml e faça as seguintes alterações:

  • Altere cada ocorrência de "ContosoUniversity" para "Contoso University". Há três ocorrências.

  • Adicione entradas de menu Sobre, Alunos, Cursos, Instrutores e Departamentos e exclua a entrada de menu Privacy.

As alterações são realçadas.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
              crossorigin="anonymous"
              integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/>
    </environment>
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Courses" asp-action="Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Departments" asp-action="Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <partial name="_CookieConsentPartial" />
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2019 - Contoso University - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>

    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    </environment>
    <environment exclude="Development">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
        </script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">
        </script>
    </environment>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

Em Views/Home/Index.cshtml, substitua o conteúdo do arquivo pelo seguinte código para substituir o texto sobre o ASP.NET e MVC pelo texto sobre este aplicativo:

@{
    ViewData["Title"] = "Home Page";
}

<div class="jumbotron">
    <h1>Contoso University</h1>
</div>
<div class="row">
    <div class="col-md-4">
        <h2>Welcome to Contoso University</h2>
        <p>
            Contoso University is a sample application that
            demonstrates how to use Entity Framework Core in an
            ASP.NET Core MVC web application.
        </p>
    </div>
    <div class="col-md-4">
        <h2>Build it from scratch</h2>
        <p>You can build the application by following the steps in a series of tutorials.</p>
        <p><a class="btn btn-default" href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the tutorial &raquo;</a></p>
    </div>
    <div class="col-md-4">
        <h2>Download it</h2>
        <p>You can download the completed project from GitHub.</p>
        <p><a class="btn btn-default" href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-mvc/intro/samples/cu-final">See project source code &raquo;</a></p>
    </div>
</div>

Pressione CTRL+F5 para executar o projeto ou escolha Depurar> Iniciar sem Depuração no menu. Você verá a home page com guias para as páginas que você criará nestes tutoriais.

Contoso University home page

Sobre os pacotes EF Core NuGet

Para adicionar o suporte do EF Core a um projeto, instale o provedor de banco de dados que você deseja ter como destino. Este tutorial usa o SQL Server e o pacote de provedor é Microsoft.EntityFrameworkCore.SqlServer. Esse pacote está incluído no metapacote Microsoft.AspNetCore.App, portanto, você não precisa referenciar o pacote.

O pacote SQL Server do EF e suas dependências (Microsoft.EntityFrameworkCore e Microsoft.EntityFrameworkCore.Relational) fornecem suporte de runtime para o EF. Você adicionará um pacote de ferramentas posteriormente, no tutorial Migrações.

Para obter informações sobre outros provedores de banco de dados que estão disponíveis para o Entity Framework Core, consulte Provedores de banco de dados.

Criar o modelo de dados

Em seguida, você criará as classes de entidade para o aplicativo Contoso University. Você começará com as três entidades a seguir.

Course-Enrollment-Student data model diagram

Há uma relação um-para-muitos entre as entidades Student e Enrollment, e uma relação um-para-muitos entre as entidades Course e Enrollment. Em outras palavras, um aluno pode ser registrado em qualquer quantidade de cursos e um curso pode ter qualquer quantidade de alunos registrados.

Nas seções a seguir, você criará uma classe para cada uma dessas entidades.

A entidade Student

Student entity diagram

Na pasta Modelos, crie um arquivo de classe chamado Student.cs e substitua o código de modelo pelo código a seguir.

using System;
using System.Collections.Generic;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

A propriedade ID se tornará a coluna de chave primária da tabela de banco de dados que corresponde a essa classe. Por padrão, o Entity Framework interpreta uma propriedade chamada ID ou classnameID como a chave primária.

A propriedade Enrollments é uma propriedade de navegação. As propriedades de navegação armazenam outras entidades que estão relacionadas a essa entidade. Nesse caso, a propriedade Enrollments de uma Student entity armazenará todas as entidades Enrollment relacionadas a essa entidade Student. Em outras palavras, se uma linha Student no banco de dados tiver duas linhas Enrollment relacionadas (linhas que contêm o valor de chave primária do aluno na coluna de chave estrangeira StudentID), a propriedade de navegação Enrollments dessa entidade Student conterá as duas entidades Enrollment.

Se uma propriedade de navegação pode armazenar várias entidades (como em relações muitos para muitos ou um-para-muitos), o tipo precisa ser uma lista na qual entradas podem ser adicionadas, excluídas e atualizadas, como ICollection<T>. Especifique ICollection<T> ou um tipo, como List<T> ou HashSet<T>. Se você especificar ICollection<T>, o EF criará uma coleção HashSet<T> por padrão.

A entidade Enrollment

Enrollment entity diagram

Na pasta Modelos, crie Enrollment.cs e substitua o código existente pelo seguinte código:

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

A propriedade EnrollmentID será a chave primária; essa entidade usa o padrão classnameID em vez de ID por si só, como você viu na entidade Student. Normalmente, você escolhe um padrão e usa-o em todo o modelo de dados. Aqui, a variação ilustra que você pode usar qualquer um dos padrões. Em um tutorial posterior, você verá como usar uma ID sem nome de classe facilita a implementação da herança no modelo de dados.

A propriedade Grade é um enum. O ponto de interrogação após a declaração de tipo Grade indica que a propriedade Grade permite valor nulo. Uma nota nula é diferente de uma nota zero – nulo significa que uma nota não é conhecida ou que ainda não foi atribuída.

A propriedade StudentID é uma chave estrangeira e a propriedade de navegação correspondente é Student. Uma entidade Enrollment é associada a uma entidade Student, de modo que a propriedade possa armazenar apenas uma única entidade Student (ao contrário da propriedade de navegação Student.Enrollments que você viu anteriormente, que pode armazenar várias entidades Enrollment).

A propriedade CourseID é uma chave estrangeira e a propriedade de navegação correspondente é Course. Uma entidade Enrollment está associada a uma entidade Course.

O Entity Framework interpreta uma propriedade como uma propriedade de chave estrangeira se ela é nomeada <navigation property name><primary key property name> (por exemplo, StudentID para a propriedade de navegação Student, pois a chave primária da entidade Student é ID). As propriedades de chave estrangeira também podem ser nomeadas apenas <primary key property name> (por exemplo, CourseID, pois a chave primária da entidade Course é CourseID).

A entidade Course

Course entity diagram

Na pasta Modelos, crie Course.cs e substitua o código existente pelo seguinte código:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

A propriedade Enrollments é uma propriedade de navegação. Uma entidade Course pode estar relacionada a qualquer quantidade de entidades Enrollment.

Falaremos mais sobre o atributo DatabaseGenerated em um tutorial posterior desta série. Basicamente, esse atributo permite que você insira a chave primária do curso, em vez de fazer com que ela seja gerada pelo banco de dados.

Criar o contexto de banco de dados

A classe principal que coordena a funcionalidade do Entity Framework para determinado modelo de dados é a classe de contexto de banco de dados. Você cria essa classe derivando-a da classe Microsoft.EntityFrameworkCore.DbContext. No código, especifique quais entidades são incluídas no modelo de dados. Também personalize o comportamento específico do Entity Framework. Neste projeto, a classe é chamada SchoolContext.

Na pasta do projeto, crie uma pasta chamada Dados.

Na pasta Dados, crie um novo arquivo de classe chamado SchoolContext.cs e substitua o código do modelo pelo seguinte código:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
        {
        }

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }
    }
}

Esse código cria uma propriedade DbSet para cada conjunto de entidades. Na terminologia do Entity Framework, um conjunto de entidades normalmente corresponde a uma tabela de banco de dados, enquanto uma entidade corresponde a uma linha na tabela.

Você pode omitir as instruções DbSet<Enrollment> e DbSet<Course> e elas funcionarão da mesma maneira. O Entity Framework inclui-os de forma implícita porque a entidade Student referencia a entidade Enrollment e a entidade Enrollment referencia a entidade Course.

Quando o banco de dados é criado, o EF cria tabelas que têm nomes iguais aos nomes de propriedade DbSet. Em geral, os nomes de propriedade de coleções são plurais (Alunos em vez de Aluno), mas os desenvolvedores não concordam sobre se os nomes de tabela devem ser pluralizados ou não. Para esses tutoriais, você substituirá o comportamento padrão especificando nomes singulares de tabela no DbContext. Para fazer isso, adicione o código realçado a seguir após a última propriedade DbSet.

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
        {
        }

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

Compile o projeto como uma verificação de erros do compilador.

Registrar o SchoolContext

O ASP.NET Core implementa a injeção de dependência por padrão. Serviços (como o contexto de banco de dados do EF) são registrados com injeção de dependência durante a inicialização do aplicativo. Os componentes que exigem esses serviços (como controladores MVC) recebem esses serviços por meio de parâmetros do construtor. Você verá o código de construtor do controlador que obtém uma instância de contexto mais adiante neste tutorial.

Para registrar SchoolContext como um serviço, abra Startup.cs e adicione as linhas realçadas ao método ConfigureServices.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDbContext<SchoolContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddMvc();
}

O nome da cadeia de conexão é passado para o contexto com a chamada de um método em um objeto DbContextOptionsBuilder. Para o desenvolvimento local, o sistema de configuração do ASP.NET Core lê a cadeia de conexão do arquivo appsettings.json.

Adicione instruções using aos namespaces ContosoUniversity.Data e Microsoft.EntityFrameworkCore e, em seguida, compile o projeto.

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Http;

Abra o arquivo appsettings.json e adicione uma cadeia de conexão, conforme mostrado no exemplo a seguir.

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  }
}

SQL Server Express LocalDB

A cadeia de conexão especifica um banco de dados LocalDB do SQL Server. LocalDB é uma versão leve do Mecanismo de Banco de Dados do SQL Server Express destinado ao desenvolvimento de aplicativos, e não ao uso em produção. O LocalDB é iniciado sob demanda e executado no modo de usuário e, portanto, não há nenhuma configuração complexa. Por padrão, o LocalDB cria arquivos de banco de dados .mdf no diretório C:/Users/<user>.

Inicializar o BD com os dados de teste

O Entity Framework criará um banco de dados vazio para você. Nesta seção, você escreve um método que é chamado depois que o banco de dados é criado para populá-lo com os dados de teste.

Aqui, você usará o método EnsureCreated para criar o banco de dados automaticamente. Em um tutorial posterior, você verá como manipular as alterações do modelo usando as Migrações do Code First para alterar o esquema de banco de dados, em vez de remover e recriar o banco de dados.

Na pasta Data, crie um novo arquivo de classe chamado DbInitializer.cs e substitua o código de modelo pelo código a seguir, que faz com que um banco de dados seja criado, quando necessário, e carrega dados de teste no novo banco de dados.

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
            new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
            new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
            new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
            };
            foreach (Student s in students)
            {
                context.Students.Add(s);
            }
            context.SaveChanges();

            var courses = new Course[]
            {
            new Course{CourseID=1050,Title="Chemistry",Credits=3},
            new Course{CourseID=4022,Title="Microeconomics",Credits=3},
            new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
            new Course{CourseID=1045,Title="Calculus",Credits=4},
            new Course{CourseID=3141,Title="Trigonometry",Credits=4},
            new Course{CourseID=2021,Title="Composition",Credits=3},
            new Course{CourseID=2042,Title="Literature",Credits=4}
            };
            foreach (Course c in courses)
            {
                context.Courses.Add(c);
            }
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
            new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
            new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
            new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
            new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
            new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
            new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
            new Enrollment{StudentID=3,CourseID=1050},
            new Enrollment{StudentID=4,CourseID=1050},
            new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
            new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
            new Enrollment{StudentID=6,CourseID=1045},
            new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };
            foreach (Enrollment e in enrollments)
            {
                context.Enrollments.Add(e);
            }
            context.SaveChanges();
        }
    }
}

O código verifica se há alunos no banco de dados e, se não há, ele pressupõe que o banco de dados é novo e precisa ser propagado com os dados de teste. Ele carrega os dados de teste em matrizes em vez de em coleções List<T> para otimizar o desempenho.

Em Program.cs, modifique o método Main para fazer o seguinte na inicialização do aplicativo:

  • Obtenha uma instância de contexto de banco de dados do contêiner de injeção de dependência.
  • Chame o método de semente passando a ele o contexto.
  • Descarte o contexto quando o método de semente for concluído.
using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

A primeira vez que você executar o aplicativo, o banco de dados será criado e propagado com os dados de teste. Sempre que você alterar o modelo de dados:

  • Exclua o banco de dados.
  • Atualize o método de semente e comece de novo com um novo banco de dados da mesma forma.

Nos próximos tutoriais, você verá como modificar o banco de dados quando o modelo de dados for alterado, sem excluí-lo e recriá-lo.

Criar um controlador e exibições

Nessa seção, o mecanismo de scaffolding no Visual Studio é usado para adicionar um controlador MVC e exibições que usam o EF para consultar e salvar dados.

A criação automática de métodos de ação CRUD e exibições é conhecida como scaffolding. O scaffolding difere da geração de código, em que o código gerado por scaffolding é um ponto de partida que você pode modificar de acordo com seus requisitos, enquanto que normalmente o código gerado não é modificado. Quando precisar personalizar o código gerado, use classes parciais ou regenere o código quando as coisas mudarem.

  • Clique com o botão direito do mouse na pasta Controladores no Gerenciador de Soluções e selecione Adicionar > Novo Item Gerado por Scaffolding.
  • Na caixa de diálogo Adicionar Scaffolding:
    • Selecione Controlador MVC com exibições, usando o Entity Framework.
    • Clique em Adicionar. A caixa de diálogo Adicionar Controlador MVC com exibições, usando o Entity Framework é exibida: Scaffold Student
    • Na classe Model, selecione Aluno.
    • Na Classe de contexto de dados selecione SchoolContext.
    • Aceite o StudentsController padrão como o nome.
    • Clique em Adicionar.

O mecanismo de scaffolding do Visual Studio cria um arquivo StudentsController.cs e um conjunto de exibições (arquivos .cshtml) que funcionam com o controlador.

Observe que o controlador usa um SchoolContext como parâmetro de construtor.

namespace ContosoUniversity.Controllers
{
    public class StudentsController : Controller
    {
        private readonly SchoolContext _context;

        public StudentsController(SchoolContext context)
        {
            _context = context;
        }

A injeção de dependência do ASP.NET Core é responsável por passar uma instância de SchoolContext para o controlador. Isso foi configurado no arquivo Startup.cs.

O controlador contém um método de ação Index, que exibe todos os alunos no banco de dados. O método obtém uma lista de alunos do conjunto de entidades Students pela leitura da propriedade Students da instância de contexto de banco de dados:

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}

Você conhecerá os elementos de programação assíncronos nesse código mais adiante no tutorial.

A exibição Views/Students/Index.cshtml exibe esta lista em uma tabela:

@model IEnumerable<ContosoUniversity.Models.Student>

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
                <th>
                    @Html.DisplayNameFor(model => model.LastName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstMidName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.EnrollmentDate)
                </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Pressione CTRL+F5 para executar o projeto ou escolha Depurar> Iniciar sem Depuração no menu.

Clique na guia Alunos para ver os dados de teste inserido pelo método DbInitializer.Initialize. Dependendo da largura da janela do navegador, você verá o link da guia Students na parte superior da página ou precisará clicar no ícone de navegação no canto superior direito para ver o link.

Contoso University home page narrow

Students Index page

Exibir o banco de dados

Quando você iniciou o aplicativo, o método DbInitializer.Initialize chamou EnsureCreated. O EF observou que não havia nenhum banco de dados e, portanto, ele criou um; em seguida, o restante do código do método Initialize populou o banco de dados com os dados. Use o SSOX (Pesquisador de Objetos do SQL Server) para exibir o banco de dados no Visual Studio.

Feche o navegador.

Se a janela do SSOX ainda não estiver aberta, selecione-a no menu Exibir do Visual Studio.

No SSOX, clique em (localdb)\MSSQLLocalDB > Bancos de Dados e, em seguida, clique na entrada do nome do banco de dados que está na cadeia de conexão no arquivo appsettings.json.

Expanda o nó Tabelas para ver as tabelas no banco de dados.

Tables in SSOX

Clique com o botão direito do mouse na tabela Aluno e clique em Exibir Dados para ver as colunas criadas e as linhas que foram inseridas na tabela.

Student table in SSOX

Os arquivos de banco de dados .mdf e .ldf estão na pasta C:\Users\<username>.

Como você está chamando EnsureCreated no método inicializador executado na inicialização do aplicativo, agora você pode fazer uma alteração na classe Student, excluir o banco de dados, executar novamente o aplicativo e o banco de dados será recriado automaticamente para que ele corresponda à alteração. Por exemplo, se você adicionar uma propriedade EmailAddress à classe Student, verá uma nova coluna EmailAddress na tabela recriada.

Convenções

A quantidade de código feita para que o Entity Framework possa criar um banco de dados completo para você é mínima, devido ao uso de convenções ou de suposições feitas pelo Entity Framework.

  • Os nomes de propriedades DbSet são usadas como nomes de tabela. Para entidades não referenciadas por uma propriedade DbSet, os nomes de classe de entidade são usados como nomes de tabela.
  • Os nomes de propriedade de entidade são usados para nomes de coluna.
  • As propriedades de entidade que são nomeadas ID ou classnameID são reconhecidas como propriedades de chave primária.
  • Uma propriedade é interpretada como sendo de chave estrangeira se for chamada de <nome da propriedade de navegação><nome da propriedade de chave primária> (por exemplo, StudentID para a propriedade de navegação Student, pois a chave primária da entidade Student é ID). As propriedades de chave estrangeira também podem ser chamadas apenas de <nome da propriedade da chave primária> (por exemplo, EnrollmentID, pois a chave primária da entidade Enrollment é EnrollmentID).

O comportamento convencional pode ser substituído. Por exemplo, você pode especificar os nomes de tabela de forma explícita, conforme visto anteriormente neste tutorial. Além disso, você pode definir nomes de coluna e qualquer propriedade como a chave primária ou chave estrangeira, como você verá em um tutorial posterior desta série.

Código assíncrono

A programação assíncrona é o modo padrão do ASP.NET Core e do EF Core.

Um servidor Web tem um número limitado de threads disponíveis e, em situações de alta carga, todos os threads disponíveis podem estar em uso. Quando isso acontece, o servidor não pode processar novas solicitações até que os threads são liberados. Com um código síncrono, muitos threads podem ser vinculados enquanto realmente não são fazendo nenhum trabalho porque estão aguardando a conclusão da E/S. Com um código assíncrono, quando um processo está aguardando a conclusão da E/S, seu thread é liberado para o servidor para ser usado para processar outras solicitações. Como resultado, o código assíncrono permite que os recursos do servidor sejam usados com mais eficiência, e o servidor fica capacitado a manipular mais tráfego sem atrasos.

O código assíncrono introduz uma pequena quantidade de sobrecarga em tempo de execução, mas para situações de baixo tráfego, o impacto no desempenho é insignificante, ao passo que, em situações de alto tráfego, a melhoria de desempenho potencial é significativa.

No código a seguir, a palavra-chave async, o valor retornado Task<T>, a palavra-chave await e o método ToListAsync fazem o código ser executado de forma assíncrona.

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}
  • A palavra-chave async instrui o compilador a gerar retornos de chamada para partes do corpo do método e a criar automaticamente o objeto Task<IActionResult> que é retornado.
  • O tipo de retorno Task<IActionResult> representa um trabalho em andamento com um resultado do tipo IActionResult.
  • A palavra-chave await faz com que o compilador divida o método em duas partes. A primeira parte termina com a operação que é iniciada de forma assíncrona. A segunda parte é colocada em um método de retorno de chamada que é chamado quando a operação é concluída.
  • ToListAsync é a versão assíncrona do método de extensão ToList.

Algumas coisas a serem consideradas quando você estiver escrevendo um código assíncrono que usa o Entity Framework:

  • Somente instruções que fazem com que consultas ou comandos sejam enviados ao banco de dados são executadas de forma assíncrona. Isso inclui, por exemplo, ToListAsync, SingleOrDefaultAsync e SaveChangesAsync. Isso não inclui, por exemplo, instruções que apenas alteram um IQueryable, como var students = context.Students.Where(s => s.LastName == "Davolio").
  • Um contexto do EF não é thread-safe: não tente realizar várias operações em paralelo. Quando você chamar qualquer método assíncrono do EF, sempre use a palavra-chave await.
  • Se desejar aproveitar os benefícios de desempenho do código assíncrono, verifique se os pacotes de biblioteca que você está usando (por exemplo, para paginação) também usam o código assíncrono se eles chamam métodos do Entity Framework que fazem com que consultas sejam enviadas ao banco de dados.

Para obter mais informações sobre a programação assíncrona no .NET, consulte Visão geral da programação assíncrona.

Próximas etapas

Vá para o próximo tutorial para aprender a executar operações CRUD (criar, ler, atualizar e excluir) básicas.