Razor Pages com o Entity Framework Core no ASP.NET Core – Tutorial 1 de 8

Por Tom Dykstra, Jeremy Likness e Jon P. Smith

Este é o primeiro de uma série de tutoriais que mostram como usar o EF (Entity Framework) Core em um aplicativo Razor Pages do ASP.NET Core. O tutorial cria um site de uma Contoso University fictícia. O site inclui funcionalidades como admissão de alunos, criação de cursos e atribuições de instrutor. O tutorial usa a abordagem de priorização de código. Para obter informações sobre como seguir este tutorial usando a abordagem de priorização do banco de dados, confira este problema do Github.

Baixe ou exiba o aplicativo completo.Baixe as instruções.

Pré-requisitos

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.

Solução de problemas

Se você encontrar um problema que não possa resolver, compare seu código com o projeto concluído. Uma boa maneira de obter ajuda é postando uma pergunta no StackOverflow.com usando a tag ASP.NET Core ou a tag do EF Core.

O aplicativo de exemplo

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. Veja a seguir algumas das telas criadas no tutorial.

Students Index page

Students Edit page

O estilo de interface do usuário deste site baseia-se nos modelos de projeto internos. O foco do tutorial está em como usar o EF Core com o ASP.NET Core, não em como personalizar a interface do usuário.

Opcional: compilar o download de exemplo

Esta etapa é opcional. A criação do aplicativo completo é recomendada quando você tem problemas que não consegue resolver. Se você encontrar um problema que não possa resolver, compare seu código com o projeto concluído. Instruções de download.

Selecione ContosoUniversity.csproj para abrir o projeto.

  • Compile o projeto.

  • No PMC (Console do Gerenciador de Pacotes), execute o seguinte comando:

    Update-Database
    

Execute o projeto para propagar o banco de dados.

Criar o projeto de aplicativo Web

  1. Inicie o Visual Studio 2022 e selecione Criar um novo projeto.

    Create a new project from the start window

  2. Na caixa de diálogo Criar um projeto novo, selecione Aplicativo Web ASP.NET Core e selecione Avançar.

    Create an ASP.NET Core Web App

  3. Na caixa de diálogo Configurar seu novo projeto, insira ContosoUniversity no Nome do projeto. É importante nomear o projeto ContosoUniversity, incluindo a correspondência da capitalização, para que os namespaces correspondam quando você copiar e colar o código de exemplo.

  4. Selecione Avançar.

  5. No diálogo Informações adicionais, selecione .NET 6.0 (Suporte de longo prazo) e escolha Criar.

    Additional information

Configurar o estilo do site

Copie e cole o seguinte código no arquivo Pages/Shared/_Layout.cshtml:

<!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" asp-append-version="true" />
    <link rel="stylesheet" href="~/ContosoUniversity.styles.css" asp-append-version="true" />
</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-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-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-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/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; 2021 - Contoso University - <a asp-area="" asp-page="/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>

O arquivo de layout define o cabeçalho, o rodapé e o menu do site. O código anterior faz as seguintes alterações:

  • Cada ocorrência de "ContosoUniversity" para "Contoso University". Há três ocorrências.
  • As entradas de menu Home e Privacy são excluídas.
  • Entradas são adicionadas para Sobre, Alunos, Cursos, Instrutores e Departamentos.

Em Pages/Index.cshtml, substitua o conteúdo do arquivo pelo seguinte código:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
@*                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
*@                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
@*                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
*@                </p>
            </div>
        </div>
    </div>
</div>

O código anterior substitui o texto sobre ASP.NET Core pelo texto sobre esse aplicativo.

Execute o aplicativo para verificar se o página inicial aparece.

O modelo de dados

As seções a seguir criam um modelo de dados:

Course-Enrollment-Student data model diagram

Um aluno pode ser registrado em qualquer quantidade de cursos e um curso pode ter qualquer quantidade de alunos registrados.

A entidade Student

Student entity diagram

  • Crie uma pasta Modelos na pasta do projeto.
  • Crie Models/Student.cs com o seguinte código:
    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 da tabela de banco de dados que corresponde a essa classe. Por padrão, o EF Core interpreta uma propriedade chamada ID ou classnameID como a chave primária. Portanto, o nome alternativo reconhecido automaticamente para a chave primária da classe Student é StudentID. Para mais informações, confira EF Core – Chaves.

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 entidade Student armazena todas as entidades Enrollment relacionadas àquele Aluno. Por exemplo, se uma linha Aluno no banco de dados tiver duas linhas Registro relacionadas, a propriedade de navegação Enrollments conterá duas entidades de Registro.

No banco de dados, uma linha de Registro estará relacionada a uma linha de Aluno se sua coluna StudentID contiver o valor da ID do aluno. Por exemplo, suponha que uma linha de aluno tenha ID=1. As linhas de registro relacionadas terão StudentID = 1. StudentID é uma chave estrangeira na tabela Registro.

A propriedade Enrollments é definida como ICollection<Enrollment> porque pode haver várias entidades de registro relacionadas. Outros tipos de coleção podem ser usados, como List<Enrollment> ou HashSet<Enrollment>. Quando ICollection<Enrollment> é usado, o EF Core cria uma coleção HashSet<Enrollment> por padrão.

A entidade Enrollment

Enrollment entity diagram

Crie Models/Enrollment.cs com o seguinte código:

using System.ComponentModel.DataAnnotations;

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; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

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

A propriedade EnrollmentID é a chave primária; essa entidade usa o padrão classnameID, em vez de ID por si mesmo. Para um modelo de dados de produção, muitos desenvolvedores escolhem um padrão e usam-no de forma consistente. Este tutorial usa ambos apenas para ilustrar que os dois funcionam. Usar ID sem classname facilita a implementação de alguns tipos de alterações 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 anulável. 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 está associada a uma entidade Student e, portanto, a propriedade contém uma única entidade Student.

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

O EF Core interpreta uma propriedade como uma chave estrangeira se ela é nomeada <navigation property name><primary key property name>. Por exemplo, StudentID é a chave estrangeira para a propriedade de navegação Student, pois a chave primária da entidade Student é ID. Propriedades de chave estrangeira também podem ser nomeadas <primary key property name>. Por exemplo, CourseID, pois a chave primária da entidade Course é CourseID.

A entidade Course

Course entity diagram

Crie Models/Course.cs com o seguinte código:

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 permite que o aplicativo especifique a chave primária em vez de fazer com que ela seja gerada pelo banco de dados.

Crie o aplicativo. O compilador gera vários avisos sobre como os valores de null são tratados. Veja este problema do GitHub, Tipos de referência anuláveis e Tutorial: Expresse sua intenção de design com mais clareza com tipos de referência anuláveis e não anuláveis para obter mais informações.

Para eliminar os avisos dos tipos de referência anuláveis, remova a seguinte linha do arquivo ContosoUniversity.csproj:

<Nullable>enable</Nullable>

Atualmente, o mecanismo de scaffolding não dá suporte a tipos de referência anuláveis, ou seja, os modelos usados no scaffold também não dão.

Remova a anotação de tipo de referência anulável ? de public string? RequestId { get; set; } em Pages/Error.cshtml.cs para que o projeto seja compilado sem avisos do compilador.

Aplicar scaffold a páginas de Aluno

Nesta seção, a ferramenta de scaffolding do ASP.NET Core é usada para gerar:

  • Uma classe EF CoreDbContext. O contexto é a classe principal que coordena a funcionalidade do Entity Framework para determinado modelo de dados. Ele deriva da classe Microsoft.EntityFrameworkCore.DbContext.
  • Razor Pages que lidam com as operações CRUD (criar, ler, atualizar e excluir) para a entidade Student.
  • Crie uma pasta Pages/Students.
  • No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Páginas/Alunos e selecione Adicionar>Novo Item com Scaffold.
  • No diálogo Adicionar Novo Item de Scaffold:
    • Na guia à esquerda, selecione Razor Pages> Comuns >Instaladas
    • Selecione Razor Pages usando Entity Framework (CRUD)>ADD.
  • Na caixa de diálogo Adicionar Razor Pages usando o Entity Framework (CRUD):
    • Na lista suspensa classe Modelo, selecione Aluno (ContosoUniversity.Models).
    • Na linha Classe de contexto de dados, selecione o sinal + (adição).
      • Altere o nome do contexto de dados para terminar em SchoolContext em vez de ContosoUniversityContext. O nome de contexto atualizado: ContosoUniversity.Data.SchoolContext
      • Selecione Adicionar para concluir a adição da classe de contexto de dados.
      • Selecione Adicionar para concluir o diálogo Adicionar Razor Pages.

Os seguintes pacotes são instalados automaticamente:

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

Se a etapa anterior falhar, crie o projeto e repita a etapa de scaffold.

O processo de scaffolding:

  • Cria Razor Pages na pasta Pages/Students:
    • Create.cshtml e Create.cshtml.cs
    • Delete.cshtml e Delete.cshtml.cs
    • Details.cshtml e Details.cshtml.cs
    • Edit.cshtml e Edit.cshtml.cs
    • Index.cshtml e Index.cshtml.cs
  • Cria Data/SchoolContext.cs.
  • Adiciona o contexto à injeção de dependência em Program.cs.
  • Adicionar uma cadeia de conexão de banco de dados a appsettings.json.

Cadeia de conexão de banco de dados

A ferramenta scaffolding gera uma cadeia de conexão no arquivo appsettings.json.

A cadeia de conexão especifica um LocalDB do SQL Server:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext-0e9;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

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. Por padrão, o LocalDB cria arquivos .mdf no diretório C:/Users/<user>.

Atualizar a classe do contexto de banco de dados

A classe principal que coordena a funcionalidade do EF Core de um modelo de dados é a classe de contexto de banco de dados. O contexto é derivado de Microsoft.EntityFrameworkCore.DbContext. O contexto especifica quais entidades são incluídas no modelo de dados. Neste projeto, a classe é chamada SchoolContext.

Atualize Data/SchoolContext.cs com o seguinte código:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

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

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

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

O código anterior muda do DbSet<Student> Student singular para o DbSet<Student> Students plural. Para fazer com que o código do Razor Pages corresponda ao novo nome de DBSet, faça uma alteração global de: _context.Student. para: _context.Students.

Há oito ocorrências.

Como um conjunto de entidades contém várias entidades, muitos desenvolvedores preferem que os nomes de propriedades DBSet fiquem no plural.

O código realçado:

  • Cria uma propriedade DbSet<TEntity> para cada conjunto de entidades. Na terminologia do EF Core:
    • Um conjunto de entidades normalmente corresponde a uma tabela de banco de dados.
    • Uma entidade corresponde a uma linha da tabela.
  • Chama OnModelCreating. OnModelCreating:
    • É chamado quando SchoolContext foi inicializado, mas antes do modelo ter sido bloqueado e usado para inicializar o contexto.
    • É necessário porque, mais adiante no tutorial, a entidade Student terá referências às outras entidades.

Esperamos corrigir esse problema em uma versão futura.

Module.vb

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

A ferramenta de scaffolding registrou automaticamente a classe de contexto com o contêiner de injeção de dependência.

As linhas destacadas abaixo foram adicionadas pelo scaffolder:

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

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

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

Adicione AddDatabaseDeveloperPageExceptionFilter e UseMigrationsEndPoint conforme mostrado no código abaixo:

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();
}

Adicione o pacote NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.

No Console do Gerenciador de Pacotes, insira o seguinte para adicionar o pacote NuGet:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

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

O AddDatabaseDeveloperPageExceptionFilter fornece informações de erro úteis no ambiente de desenvolvimento em erros de migrações de EF.

Criar o banco de dados

Atualize Program.cs para criar o banco de dados se ele não existir:

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();
}

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    // DbInitializer.Initialize(context);
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

O método EnsureCreated não executará nenhuma ação se existir um banco de dados para o contexto. Se não existir nenhum banco de dados, ele criará o banco de dados e o esquema. EnsureCreated habilita o seguinte fluxo de trabalho para manipular alterações no modelo de dados:

  • Exclua o banco de dados. Qualquer dado existente é perdido.
  • Altere o modelo de dados. Por exemplo, adicione um campo EmailAddress.
  • Execute o aplicativo.
  • EnsureCreated cria um banco de dados com o novo esquema.

Esse fluxo de trabalho funciona no início do desenvolvimento, quando o esquema está evoluindo rapidamente, desde que os dados não precisem ser preservados. A situação é diferente quando os dados que foram inseridos no banco de dados precisam ser preservados. Quando esse for o caso, use migrações.

Posteriormente na série de tutoriais, o banco de dados criado por EnsureCreated é excluído e as migrações são usadas. Um banco de dados criado pelo EnsureCreated não pode ser atualizado usando migrações.

Testar o aplicativo

  • Execute o aplicativo.
  • Selecione o link Alunos e Criar Novo.
  • Teste os links Editar, Detalhes e Excluir.

Propagar o banco de dados

O método EnsureCreated cria um banco de dados vazio. Esta seção adiciona um código que preenche o banco de dados com os dados de teste.

Crie Data/DbInitializer.cs com o seguinte código:

using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // 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("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            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}
            };

            context.Courses.AddRange(courses);
            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},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

O código verifica se há alunos no banco de dados. Se não houver nenhum aluno, ele adicionará dados de teste ao banco de dados. Ele carrega os dados de teste em matrizes, em vez de em coleções de List<T>, para otimizar o desempenho.

  • Em Program.cs, remova // da linha DbInitializer.Initialize:
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
}
  • Interrompa o aplicativo se ele estiver em execução e execute o seguinte comando no PMC (Console do Gerenciador de Pacotes):

    Drop-Database -Confirm
    
    
  • Responda com Y para excluir o banco de dados.

  • Reinicie o aplicativo.
  • Selecione a página Alunos para ver os dados propagados.

Exibição do banco de dados

  • Abra o SSOX (Pesquisador de Objetos do SQL Server) no menu Exibir do Visual Studio.
  • No SSOX, selecione (localdb)\MSSQLLocalDB > Bancos de Dados > SchoolContext-{GUID}. O nome do banco de dados é gerado usando o nome do contexto fornecido anteriormente, além de um traço e um GUID.
  • Expanda o nó Tabelas.
  • Clique com o botão direito do mouse na tabela Aluno e clique em Exibir Dados para ver as colunas criadas e as linhas inseridas na tabela.
  • Clique com o botão direito do mouse na tabela Aluno e clique em Exibir Código para ver como o modelo Student é mapeado para o esquema de tabela Student.

Métodos EF assíncronos em aplicativos Web do ASP.NET Core

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 não estão fazendo 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 pode manipular mais tráfego sem atrasos.

O código assíncrono introduz uma pequena quantidade de sobrecarga em tempo de execução. Para situações de baixo tráfego, o impacto no desempenho é insignificante, enquanto para 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, a palavra-chave await e o método ToListAsync fazem o código ser executado de forma assíncrona.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • A palavra-chave async instrui o compilador a:
    • Gerar retornos de chamada para partes do corpo do método.
    • Criar o objeto Task que é retornado.
  • O tipo retornado Task representa um trabalho em andamento.
  • 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 escrever um código assíncrono que usa o EF Core:

  • 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 ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync e SaveChangesAsync. Isso não inclui instruções que apenas alteram um IQueryable, como var students = context.Students.Where(s => s.LastName == "Davolio").
  • Um contexto do EF Core não é thread-safe: não tente realizar várias operações em paralelo.
  • Para aproveitar os benefícios de desempenho do código assíncrono, verifique se os pacotes de biblioteca (como para paginação) usam o código assíncrono se eles chamarem métodos do EF Core que enviam consultas ao banco de dados.

Para obter mais informações sobre a programação assíncrona, consulte Visão geral de Async e Programação assíncrona com async e await.

Aviso

A implementação assíncrona do Microsoft.Data.SqlClient tem alguns problemas conhecidos (#593, #601 e outros). Se você estiver enfrentando problemas de desempenho inesperados, tente usar a execução de comando de sincronização, especialmente ao lidar com valores binários ou de texto grande.

Considerações sobre o desempenho

Em geral, uma página da Web não deve carregar um número aleatório de linhas. Uma consulta deve usar paginação ou uma abordagem de limitação. Por exemplo, a consulta anterior poderia usar Take para limitar as linhas retornadas:

public async Task OnGetAsync()
{
    Student = await _context.Students.Take(10).ToListAsync();
}

A enumeração de uma tabela grande em um modo de exibição poderá retornar uma resposta HTTP 200 parcialmente construída se uma exceção de banco de dados ocorrer no meio da enumeração.

A paginação será abordada posteriormente no tutorial.

Para obter mais informações, confira Considerações sobre desempenho (EF).

Próximas etapas

Usar SQLite para desenvolvimento, SQL Server para produção

Este é o primeiro de uma série de tutoriais que mostram como usar o EF (Entity Framework) Core em um aplicativo Razor Pages do ASP.NET Core. O tutorial cria um site de uma Contoso University fictícia. O site inclui funcionalidades como admissão de alunos, criação de cursos e atribuições de instrutor. O tutorial usa a abordagem de priorização de código. Para obter informações sobre como seguir este tutorial usando a abordagem de priorização do banco de dados, confira este problema do Github.

Baixe ou exiba o aplicativo completo.Baixe as instruções.

Pré-requisitos

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.

Solução de problemas

Se você encontrar um problema que não possa resolver, compare seu código com o projeto concluído. Uma boa maneira de obter ajuda é postando uma pergunta no StackOverflow.com usando a tag ASP.NET Core ou a tag do EF Core.

O aplicativo de exemplo

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. Veja a seguir algumas das telas criadas no tutorial.

Students Index page

Students Edit page

O estilo de interface do usuário deste site baseia-se nos modelos de projeto internos. O foco do tutorial está em como usar o EF Core com o ASP.NET Core, não em como personalizar a interface do usuário.

Opcional: compilar o download de exemplo

Esta etapa é opcional. A criação do aplicativo completo é recomendada quando você tem problemas que não consegue resolver. Se você encontrar um problema que não possa resolver, compare seu código com o projeto concluído. Instruções de download.

Selecione ContosoUniversity.csproj para abrir o projeto.

  • Compile o projeto.
  • No PMC (Console do Gerenciador de Pacotes), execute o seguinte comando:
Update-Database

Execute o projeto para propagar o banco de dados.

Criar o projeto de 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.
    3. CriarNew ASP.NET Core Project dialog

Configurar o estilo do site

Copie e cole o seguinte código no arquivo Pages/Shared/_Layout.cshtml:

<!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-page="/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-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/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; 2021 - Contoso University - <a asp-area="" asp-page="/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>

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

O arquivo de layout define o cabeçalho, o rodapé e o menu do site. O código anterior faz as seguintes alterações:

  • Cada ocorrência de "ContosoUniversity" para "Contoso University". Há três ocorrências.
  • As entradas de menu Home e Privacy são excluídas.
  • Entradas são adicionadas para Sobre, Alunos, Cursos, Instrutores e Departamentos.

Em Pages/Index.cshtml, substitua o conteúdo do arquivo pelo seguinte código:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

O código anterior substitui o texto sobre ASP.NET Core pelo texto sobre esse aplicativo.

Execute o aplicativo para verificar se o página inicial aparece.

O modelo de dados

As seções a seguir criam um modelo de dados:

Course-Enrollment-Student data model diagram

Um aluno pode ser registrado em qualquer quantidade de cursos e um curso pode ter qualquer quantidade de alunos registrados.

A entidade Student

Student entity diagram

  • Crie uma pasta Modelos na pasta do projeto.

  • Crie Models/Student.cs 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 da tabela de banco de dados que corresponde a essa classe. Por padrão, o EF Core interpreta uma propriedade chamada ID ou classnameID como a chave primária. Portanto, o nome alternativo reconhecido automaticamente para a chave primária da classe Student é StudentID. Para mais informações, confira EF Core – Chaves.

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 entidade Student armazena todas as entidades Enrollment relacionadas àquele Aluno. Por exemplo, se uma linha Aluno no banco de dados tiver duas linhas Registro relacionadas, a propriedade de navegação Enrollments conterá duas entidades de Registro.

No banco de dados, uma linha de Registro estará relacionada a uma linha de Aluno se sua coluna StudentID contiver o valor da ID do aluno. Por exemplo, suponha que uma linha de aluno tenha ID=1. As linhas de registro relacionadas terão StudentID = 1. StudentID é uma chave estrangeira na tabela Registro.

A propriedade Enrollments é definida como ICollection<Enrollment> porque pode haver várias entidades de registro relacionadas. Outros tipos de coleção podem ser usados, como List<Enrollment> ou HashSet<Enrollment>. Quando ICollection<Enrollment> é usado, o EF Core cria uma coleção HashSet<Enrollment> por padrão.

A entidade Enrollment

Enrollment entity diagram

Crie Models/Enrollment.cs com o seguinte código:

using System.ComponentModel.DataAnnotations;

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; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

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

A propriedade EnrollmentID é a chave primária; essa entidade usa o padrão classnameID, em vez de ID por si mesmo. Para um modelo de dados de produção, muitos desenvolvedores escolhem um padrão e usam-no de forma consistente. Este tutorial usa ambos apenas para ilustrar que os dois funcionam. Usar ID sem classname facilita a implementação de alguns tipos de alterações 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 anulável. 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 está associada a uma entidade Student e, portanto, a propriedade contém uma única entidade Student.

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

O EF Core interpreta uma propriedade como uma chave estrangeira se ela é nomeada <navigation property name><primary key property name>. Por exemplo, StudentID é a chave estrangeira para a propriedade de navegação Student, pois a chave primária da entidade Student é ID. Propriedades de chave estrangeira também podem ser nomeadas <primary key property name>. Por exemplo, CourseID, pois a chave primária da entidade Course é CourseID.

A entidade Course

Course entity diagram

Crie Models/Course.cs 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 permite que o aplicativo especifique a chave primária em vez de fazer com que ela seja gerada pelo banco de dados.

Compile o projeto para validar que não há erros de compilador.

Aplicar scaffold a páginas de Aluno

Nesta seção, a ferramenta de scaffolding do ASP.NET Core é usada para gerar:

  • Uma classe EF CoreDbContext. O contexto é a classe principal que coordena a funcionalidade do Entity Framework para determinado modelo de dados. Ele deriva da classe Microsoft.EntityFrameworkCore.DbContext.
  • Razor Pages que lidam com as operações CRUD (criar, ler, atualizar e excluir) para a entidade Student.
  • Crie uma pasta Pages/Students.
  • No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Páginas/Alunos e selecione Adicionar>Novo Item com Scaffold.
  • No diálogo Adicionar Novo Item de Scaffold:
    • Na guia à esquerda, selecione Razor Pages> Comuns >Instaladas
    • Selecione Razor Pages usando Entity Framework (CRUD)>ADD.
  • Na caixa de diálogo Adicionar Razor Pages usando o Entity Framework (CRUD):
    • Na lista suspensa classe Modelo, selecione Aluno (ContosoUniversity.Models).
    • Na linha Classe de contexto de dados, selecione o sinal + (adição).
      • Altere o nome do contexto de dados para terminar em SchoolContext em vez de ContosoUniversityContext. O nome de contexto atualizado: ContosoUniversity.Data.SchoolContext
      • Selecione Adicionar para concluir a adição da classe de contexto de dados.
      • Selecione Adicionar para concluir o diálogo Adicionar Razor Pages.

Se o scaffolding falhar com o erro 'Install the package Microsoft.VisualStudio.Web.CodeGeneration.Design and try again.', execute a ferramenta de scaffold novamente ou veja este problema do GitHub.

Os seguintes pacotes são instalados automaticamente:

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

Se a etapa anterior falhar, crie o projeto e repita a etapa de scaffold.

O processo de scaffolding:

  • Cria Razor Pages na pasta Pages/Students:
    • Create.cshtml e Create.cshtml.cs
    • Delete.cshtml e Delete.cshtml.cs
    • Details.cshtml e Details.cshtml.cs
    • Edit.cshtml e Edit.cshtml.cs
    • Index.cshtml e Index.cshtml.cs
  • Cria Data/SchoolContext.cs.
  • Adiciona o contexto à injeção de dependência em Startup.cs.
  • Adicionar uma cadeia de conexão de banco de dados a appsettings.json.

Cadeia de conexão de banco de dados

A ferramenta scaffolding gera uma cadeia de conexão no arquivo appsettings.json.

A cadeia de conexão especifica um LocalDB do SQL Server:

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

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. Por padrão, o LocalDB cria arquivos .mdf no diretório C:/Users/<user>.

Atualizar a classe do contexto de banco de dados

A classe principal que coordena a funcionalidade do EF Core de um modelo de dados é a classe de contexto de banco de dados. O contexto é derivado de Microsoft.EntityFrameworkCore.DbContext. O contexto especifica quais entidades são incluídas no modelo de dados. Neste projeto, a classe é chamada SchoolContext.

Atualize Data/SchoolContext.cs com o seguinte código:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

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

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

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

O código anterior muda do DbSet<Student> Student singular para o DbSet<Student> Students plural. Para fazer com que o código do Razor Pages corresponda ao novo nome de DBSet, faça uma alteração global de: _context.Student. para: _context.Students.

Há oito ocorrências.

Como um conjunto de entidades contém várias entidades, muitos desenvolvedores preferem que os nomes de propriedades DBSet fiquem no plural.

O código realçado:

  • Cria uma propriedade DbSet<TEntity> para cada conjunto de entidades. Na terminologia do EF Core:
    • Um conjunto de entidades normalmente corresponde a uma tabela de banco de dados.
    • Uma entidade corresponde a uma linha da tabela.
  • Chama OnModelCreating. OnModelCreating:
    • É chamado quando SchoolContext foi inicializado, mas antes do modelo ter sido bloqueado e usado para inicializar o contexto.
    • É necessário porque, mais adiante no tutorial, a entidade Student terá referências às outras entidades.

Compile o projeto para verificar se não há erros de compilador.

Startup.cs

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

A ferramenta de scaffolding registrou automaticamente a classe de contexto com o contêiner de injeção de dependência.

As linhas destacadas abaixo foram adicionadas pelo scaffolder:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

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

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

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

Adicione AddDatabaseDeveloperPageExceptionFilter e UseMigrationsEndPoint conforme mostrado no código abaixo:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

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

    services.AddDatabaseDeveloperPageExceptionFilter();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Adicione o pacote NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.

No Console do Gerenciador de Pacotes, insira o seguinte para adicionar o pacote NuGet:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

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

O AddDatabaseDeveloperPageExceptionFilter fornece informações de erro úteis no ambiente de desenvolvimento em erros de migrações de EF.

Criar o banco de dados

Atualize Program.cs para criar o banco de dados se ele não existir:

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>();
                    context.Database.EnsureCreated();
                    // 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>();
                });
    }
}

O método EnsureCreated não executará nenhuma ação se existir um banco de dados para o contexto. Se não existir nenhum banco de dados, ele criará o banco de dados e o esquema. EnsureCreated habilita o seguinte fluxo de trabalho para manipular alterações no modelo de dados:

  • Exclua o banco de dados. Qualquer dado existente é perdido.
  • Altere o modelo de dados. Por exemplo, adicione um campo EmailAddress.
  • Execute o aplicativo.
  • EnsureCreated cria um banco de dados com o novo esquema.

Esse fluxo de trabalho funciona no início do desenvolvimento, quando o esquema está evoluindo rapidamente, desde que os dados não precisem ser preservados. A situação é diferente quando os dados que foram inseridos no banco de dados precisam ser preservados. Quando esse for o caso, use migrações.

Posteriormente na série de tutoriais, o banco de dados criado por EnsureCreated é excluído e as migrações são usadas. Um banco de dados criado pelo EnsureCreated não pode ser atualizado usando migrações.

Testar o aplicativo

  • Execute o aplicativo.
  • Selecione o link Alunos e Criar Novo.
  • Teste os links Editar, Detalhes e Excluir.

Propagar o banco de dados

O método EnsureCreated cria um banco de dados vazio. Esta seção adiciona um código que preenche o banco de dados com os dados de teste.

Crie Data/DbInitializer.cs 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)
        {
            // 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("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            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}
            };

            context.Courses.AddRange(courses);
            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},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

O código verifica se há alunos no banco de dados. Se não houver nenhum aluno, ele adicionará dados de teste ao banco de dados. Ele carrega os dados de teste em matrizes, em vez de em coleções de List<T>, para otimizar o desempenho.

  • Em Program.cs, remova // da linha DbInitializer.Initialize:

      context.Database.EnsureCreated();
      DbInitializer.Initialize(context);
    
  • Interrompa o aplicativo se ele estiver em execução e execute o seguinte comando no PMC (Console do Gerenciador de Pacotes):

    Drop-Database -Confirm
    
    
  • Responda com Y para excluir o banco de dados.

  • Reinicie o aplicativo.
  • Selecione a página Alunos para ver os dados propagados.

Exibição do banco de dados

  • Abra o SSOX (Pesquisador de Objetos do SQL Server) no menu Exibir do Visual Studio.
  • No SSOX, selecione (localdb)\MSSQLLocalDB > Bancos de Dados > SchoolContext-{GUID}. O nome do banco de dados é gerado usando o nome do contexto fornecido anteriormente, além de um traço e um GUID.
  • Expanda o nó Tabelas.
  • Clique com o botão direito do mouse na tabela Aluno e clique em Exibir Dados para ver as colunas criadas e as linhas inseridas na tabela.
  • Clique com o botão direito do mouse na tabela Aluno e clique em Exibir Código para ver como o modelo Student é mapeado para o esquema de tabela Student.

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 não estão fazendo 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 pode manipular mais tráfego sem atrasos.

O código assíncrono introduz uma pequena quantidade de sobrecarga em tempo de execução. Para situações de baixo tráfego, o impacto no desempenho é insignificante, enquanto para 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, a palavra-chave await e o método ToListAsync fazem o código ser executado de forma assíncrona.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • A palavra-chave async instrui o compilador a:
    • Gerar retornos de chamada para partes do corpo do método.
    • Criar o objeto Task que é retornado.
  • O tipo retornado Task representa um trabalho em andamento.
  • 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 escrever um código assíncrono que usa o EF Core:

  • 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 ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync e SaveChangesAsync. Isso não inclui instruções que apenas alteram um IQueryable, como var students = context.Students.Where(s => s.LastName == "Davolio").
  • Um contexto do EF Core não é thread-safe: não tente realizar várias operações em paralelo.
  • Para aproveitar os benefícios de desempenho do código assíncrono, verifique se os pacotes de biblioteca (como para paginação) usam o código assíncrono se eles chamarem métodos do EF Core que enviam consultas ao banco de dados.

Para obter mais informações sobre a programação assíncrona, consulte Visão geral de Async e Programação assíncrona com async e await.

Considerações sobre o desempenho

Em geral, uma página da Web não deve carregar um número aleatório de linhas. Uma consulta deve usar paginação ou uma abordagem de limitação. Por exemplo, a consulta anterior poderia usar Take para limitar as linhas retornadas:

public async Task OnGetAsync()
{
    Student = await _context.Students.Take(10).ToListAsync();
}

A enumeração de uma tabela grande em um modo de exibição poderá retornar uma resposta HTTP 200 parcialmente construída se uma exceção de banco de dados ocorrer no meio da enumeração.

MaxModelBindingCollectionSize assume o padrão de 1024. O código abaixo define MaxModelBindingCollectionSize:

public void ConfigureServices(IServiceCollection services)
{
    var myMaxModelBindingCollectionSize = Convert.ToInt32(
                Configuration["MyMaxModelBindingCollectionSize"] ?? "100");

    services.Configure<MvcOptions>(options =>
           options.MaxModelBindingCollectionSize = myMaxModelBindingCollectionSize);

    services.AddRazorPages();

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

    services.AddDatabaseDeveloperPageExceptionFilter();
}

Confira Configuração para obter informações sobre configurações como MyMaxModelBindingCollectionSize.

A paginação será abordada posteriormente no tutorial.

Para obter mais informações, confira Considerações sobre desempenho (EF).

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 tópico do GitHub.

Próximas etapas

Usar SQLite para desenvolvimento, SQL Server para produção

Este é o primeiro de uma série de tutoriais que mostram como usar o EF (Entity Framework) Core em um aplicativo Razor Pages do ASP.NET Core. O tutorial cria um site de uma Contoso University fictícia. O site inclui funcionalidades como admissão de alunos, criação de cursos e atribuições de instrutor. O tutorial usa a abordagem de priorização de código. Para obter informações sobre como seguir este tutorial usando a abordagem de priorização do banco de dados, confira este problema do Github.

Baixe ou exiba o aplicativo completo.Baixe as instruções.

Pré-requisitos

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.

As instruções do Visual Studio Code usam o SQLite, um mecanismo de banco de dados multiplataforma.

Se você optar por usar o SQLite, baixe e instale uma ferramenta de terceiros para gerenciar e exibir um banco de dados SQLite, como o Navegador de BD para SQLite.

Solução de problemas

Se você encontrar um problema que não possa resolver, compare seu código com o projeto concluído. Uma boa maneira de obter ajuda é postando uma pergunta no StackOverflow.com usando a tag ASP.NET Core ou a tag do EF Core.

O aplicativo de exemplo

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. Veja a seguir algumas das telas criadas no tutorial.

Students Index page

Students Edit page

O estilo de interface do usuário deste site baseia-se nos modelos de projeto internos. O foco do tutorial está em como usar o EF Core, não em como personalizar a interface do usuário.

Siga o link na parte superior da página para obter o código-fonte do projeto concluído. A pasta cu30 tem o código para a versão ASP.NET Core 3.0 do tutorial. Os arquivos que refletem o estado do código para os tutoriais 1-7 podem ser encontrados na pasta cu30snapshots.

Para executar o aplicativo depois de baixar o projeto concluído:

  • Compile o projeto.

  • No PMC (Console do Gerenciador de Pacotes), execute o seguinte comando:

    Update-Database
    
  • Execute o projeto para propagar o banco de dados.

Criar o projeto de aplicativo Web

  • No menu Arquivo do Visual Studio, selecione Novo>Projeto.
  • Selecione Aplicativo Web ASP.NET Core.
  • Nomeie o projeto ContosoUniversity. É importante usar esse nome exato, incluindo maiúsculas e minúsculas, para que os namespaces correspondam quando o código for copiado e colado.
  • Selecione .NET Core e ASP.NET Core 3.0 na lista suspensa e, em seguida, selecione Aplicativo Web.

Configurar o estilo do site

Configure o cabeçalho, o rodapé e o menu do site atualizando Pages/Shared/_Layout.cshtml:

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

  • Exclua as entradas de menu Home e Privacy e adicione as entradas para Sobre, Alunos, Cursos, Instrutores e Departamentos.

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

<!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-page="/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-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/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; 2019 - Contoso University - <a asp-area="" asp-page="/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>

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

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

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

Execute o aplicativo para verificar se o página inicial aparece.

O modelo de dados

As seções a seguir criam um modelo de dados:

Course-Enrollment-Student data model diagram

Um aluno pode ser registrado em qualquer quantidade de cursos e um curso pode ter qualquer quantidade de alunos registrados.

A entidade Student

Student entity diagram

  • Crie uma pasta Modelos na pasta do projeto.

  • Crie Models/Student.cs 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 da tabela de banco de dados que corresponde a essa classe. Por padrão, o EF Core interpreta uma propriedade chamada ID ou classnameID como a chave primária. Portanto, o nome alternativo reconhecido automaticamente para a chave primária da classe Student é StudentID. Para mais informações, confira EF Core – Chaves.

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 entidade Student armazena todas as entidades Enrollment relacionadas àquele Aluno. Por exemplo, se uma linha Aluno no banco de dados tiver duas linhas Registro relacionadas, a propriedade de navegação Enrollments conterá duas entidades de Registro.

No banco de dados, uma linha de Registro estará relacionada a uma linha de Aluno se sua coluna StudentID contiver o valor da ID do aluno. Por exemplo, suponha que uma linha de aluno tenha ID=1. As linhas de registro relacionadas terão StudentID = 1. StudentID é uma chave estrangeira na tabela de Registro.

A propriedade Enrollments é definida como ICollection<Enrollment> porque pode haver várias entidades de registro relacionadas. Você pode usar outros tipos de coleção, como List<Enrollment> ou HashSet<Enrollment>. Quando ICollection<Enrollment> é usado, o EF Core cria uma coleção HashSet<Enrollment> por padrão.

A entidade Enrollment

Enrollment entity diagram

Crie Models/Enrollment.cs 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 chave primária; essa entidade usa o padrão classnameID, em vez de ID por si mesmo. Para um modelo de dados de produção, escolha um padrão e use-o de forma consistente. Este tutorial usa ambos apenas para ilustrar que os dois funcionam. Usar ID sem classname facilita a implementação de alguns tipos de alterações 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 anulável. 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 está associada a uma entidade Student e, portanto, a propriedade contém uma única entidade Student.

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

O EF Core interpreta uma propriedade como uma chave estrangeira se ela é nomeada <navigation property name><primary key property name>. Por exemplo, StudentID é a chave estrangeira para a propriedade de navegação Student, pois a chave primária da entidade Student é ID. Propriedades de chave estrangeira também podem ser nomeadas <primary key property name>. Por exemplo, CourseID, pois a chave primária da entidade Course é CourseID.

A entidade Course

Course entity diagram

Crie Models/Course.cs 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 permite que o aplicativo especifique a chave primária em vez de fazer com que ela seja gerada pelo banco de dados.

Compile o projeto para validar que não há erros de compilador.

Aplicar scaffold a páginas de Aluno

Nesta seção, você usa a ferramenta de scaffolding do ASP.NET Core para gerar:

  • Uma classe context de EF Core. O contexto é a classe principal que coordena a funcionalidade do Entity Framework para determinado modelo de dados. Ele deriva da classe Microsoft.EntityFrameworkCore.DbContext.
  • Razor Pages que lidam com as operações CRUD (criar, ler, atualizar e excluir) para a entidade Student.
  • Crie uma pasta Alunos na pasta Páginas.
  • No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Páginas/Alunos e selecione Adicionar>Novo Item com Scaffold.
  • No diálogo Adicionar Scaffold, selecione Razor Pages usando o Entity Framework (CRUD)>Adicionar.
  • Na caixa de diálogo Adicionar Razor Pages usando o Entity Framework (CRUD):
    • Na lista suspensa classe Modelo, selecione Aluno (ContosoUniversity.Models).
    • Na linha Classe de contexto de dados, selecione o sinal + (adição).
    • Altere o nome do contexto de dados de ContosoUniversity.Models.ContosoUniversityContext para ContosoUniversity.Data.SchoolContext.
    • Selecione Adicionar.

Os seguintes pacotes são instalados automaticamente:

  • Microsoft.VisualStudio.Web.CodeGeneration.Design
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.Extensions.Logging.Debug
  • Microsoft.EntityFrameworkCore.Tools

Se você tiver um problema com a etapa anterior, compile o projeto e repita a etapa de scaffold.

O processo de scaffolding:

  • Cria Razor Pages na pasta Pages/Students:
    • Create.cshtml e Create.cshtml.cs
    • Delete.cshtml e Delete.cshtml.cs
    • Details.cshtml e Details.cshtml.cs
    • Edit.cshtml e Edit.cshtml.cs
    • Index.cshtml e Index.cshtml.cs
  • Cria Data/SchoolContext.cs.
  • Adiciona o contexto à injeção de dependência em Startup.cs.
  • Adicionar uma cadeia de conexão de banco de dados a appsettings.json.

Cadeia de conexão de banco de dados

O arquivo appsettings.json especifica a cadeia de conexão SQL Server LocalDB.

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

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. Por padrão, o LocalDB cria arquivos .mdf no diretório C:/Users/<user>.

Atualizar a classe do contexto de banco de dados

A classe principal que coordena a funcionalidade do EF Core de um modelo de dados é a classe de contexto de banco de dados. O contexto é derivado de Microsoft.EntityFrameworkCore.DbContext. O contexto especifica quais entidades são incluídas no modelo de dados. Neste projeto, a classe é chamada SchoolContext.

Atualize Data/SchoolContext.cs com o seguinte código:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

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

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

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

O código destacado cria uma propriedade DbSet<TEntity> para cada conjunto de entidades. Na terminologia do EF Core:

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

Como um conjunto de entidades contém várias entidades, as propriedades DBSet devem ser nomes no plural. Como a ferramenta scaffolding criou umStudent DBSet, essa etapa o altera para o Students no plural.

Para fazer com que o código do Razor Pages corresponda ao novo nome do DBSet, faça uma alteração global em todo o projeto de _context.Student para _context.Students. Há oito ocorrências.

Compile o projeto para verificar se não há erros de compilador.

Startup.cs

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

A ferramenta de scaffolding registrou automaticamente a classe de contexto com o contêiner de injeção de dependência.

  • Em ConfigureServices, as linhas destacadas foram adicionadas pelo scaffolder:

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

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

Criar o banco de dados

Atualize Program.cs para criar o banco de dados se ele não existir:

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>();
                    context.Database.EnsureCreated();
                    // 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>();
                });
    }
}

O método EnsureCreated não executará nenhuma ação se existir um banco de dados para o contexto. Se não existir nenhum banco de dados, ele criará o banco de dados e o esquema. EnsureCreated habilita o seguinte fluxo de trabalho para manipular alterações no modelo de dados:

  • Exclua o banco de dados. Qualquer dado existente é perdido.
  • Altere o modelo de dados. Por exemplo, adicione um campo EmailAddress.
  • Execute o aplicativo.
  • EnsureCreated cria um banco de dados com o novo esquema.

Esse fluxo de trabalho funciona bem no início do desenvolvimento, quando o esquema está evoluindo rapidamente, desde que você não precise preservar os dados. A situação é diferente quando os dados que foram inseridos no banco de dados precisam ser preservados. Quando esse for o caso, use migrações.

Posteriormente na série de tutoriais, você excluirá o banco de dados que foi criado pelo EnsureCreated e usará migrações em vez disso. Um banco de dados criado pelo EnsureCreated não pode ser atualizado usando migrações.

Testar o aplicativo

  • Execute o aplicativo.
  • Selecione o link Alunos e Criar Novo.
  • Teste os links Editar, Detalhes e Excluir.

Propagar o banco de dados

O método EnsureCreated cria um banco de dados vazio. Esta seção adiciona um código que preenche o banco de dados com os dados de teste.

Crie Data/DbInitializer.cs com o seguinte código:

using ContosoUniversity.Data;
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("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            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}
            };

            context.Courses.AddRange(courses);
            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},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

O código verifica se há alunos no banco de dados. Se não houver nenhum aluno, ele adicionará dados de teste ao banco de dados. Ele carrega os dados de teste em matrizes, em vez de em coleções de List<T>, para otimizar o desempenho.

  • Em Program.cs, substitua a chamada EnsureCreated por uma chamada DbInitializer.Initialize:

    // context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
    

Interrompa o aplicativo se ele estiver em execução e execute o seguinte comando no PMC (Console do Gerenciador de Pacotes):

Drop-Database
  • Reinicie o aplicativo.

  • Selecione a página Alunos para ver os dados propagados.

Exibição do banco de dados

  • Abra o SSOX (Pesquisador de Objetos do SQL Server) no menu Exibir do Visual Studio.
  • No SSOX, selecione (localdb)\MSSQLLocalDB > Bancos de Dados > SchoolContext-{GUID}. O nome do banco de dados é gerado usando o nome do contexto fornecido anteriormente, além de um traço e um GUID.
  • Expanda o nó Tabelas.
  • Clique com o botão direito do mouse na tabela Aluno e clique em Exibir Dados para ver as colunas criadas e as linhas inseridas na tabela.
  • Clique com o botão direito do mouse na tabela Aluno e clique em Exibir Código para ver como o modelo Student é mapeado para o esquema de tabela Student.

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 pode manipular mais tráfego sem atrasos.

O código assíncrono introduz uma pequena quantidade de sobrecarga em tempo de execução. Para situações de baixo tráfego, o impacto no desempenho é insignificante, enquanto para 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 OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • A palavra-chave async instrui o compilador a:
    • Gerar retornos de chamada para partes do corpo do método.
    • Criar o objeto Task que é retornado.
  • O tipo retornado Task<T> representa um trabalho em andamento.
  • 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 escrever um código assíncrono que usa o EF Core:

  • 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 ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync e SaveChangesAsync. Isso não inclui instruções que apenas alteram um IQueryable, como var students = context.Students.Where(s => s.LastName == "Davolio").
  • Um contexto do EF Core não é thread-safe: não tente realizar várias operações em paralelo.
  • Para aproveitar os benefícios de desempenho do código assíncrono, verifique se os pacotes de biblioteca (como para paginação) usam o código assíncrono se eles chamarem métodos do EF Core que enviam consultas ao banco de dados.

Para obter mais informações sobre a programação assíncrona, consulte Visão geral de Async e Programação assíncrona com async e await.

Próximas etapas