RazorPáginas com o Entity Framework Core em 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 ASP.NET Core Razor Pages. 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 primeira abordagem de código. Para obter informações sobre como seguir este tutorial usando a primeira abordagem do banco de dados, consulte este problema do Github.

Baixe ou exiba o aplicativo concluído.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 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.

Página Índice de Alunos

Página Editar Alunos

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

Opcional: compilar o download de exemplo

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

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.

    Criar um novo projeto na janela inicial

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

    Criar um aplicativo Web ASP.NET Core

  3. Na caixa de diálogo Configurar seu novo projeto , insira ContosoUniversity o 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. Na caixa de diálogo Informações Adicionais , selecione .NET 6.0 (suporte a longo prazo) e selecione Criar.

    Informações adicionais

Configurar o estilo do site

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

<!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 Home entradas e Privacy o menu são excluídos.
  • As 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 este 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:

Diagrama de Modelo de Dados Course-Enrollment-Student

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

A entidade Student

Diagrama da entidade Student

  • 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 nomeada ID ou classnameID como a chave primária. Portanto, o nome alternativo reconhecido automaticamente para a chave primária da classe Student é StudentID. Para obter mais informações, consulte 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 está relacionada a uma linha student se sua StudentID coluna 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

Diagrama da entidade Enrollment

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 o usam consistentemente. 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 ainda não é conhecida ou 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

Diagrama de entidade Curso

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 null os valores 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 de tipos de referência anuláveis, remova a seguinte linha do ContosoUniversity.csproj arquivo:

<Nullable>enable</Nullable>

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

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

Aplicar scaffold a páginas de Aluno

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

  • Uma classe EF Core DbContext . 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 páginas que lidam com operações CRUD (Criar, Ler, Atualizar e Excluir) para a Student entidade.
  • 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.
  • Na caixa de diálogo Adicionar Novo Item de Scaffold :
    • Na guia à esquerda, selecione Páginas Comuns >Razor Instaladas >
    • Selecione Razor Páginas usando o CRUD (Entity Framework)>ADD.
  • Na caixa de diálogo Adicionar Razor Páginas usando o CRUD (Entity Framework ):
    • Na lista suspensa classe Modelo, selecione Aluno (ContosoUniversity.Models).
    • Na linha Classe de contexto de dados, selecione o sinal de + (adição).
      • Altere o nome do contexto de dados para terminar em SchoolContext vez de ContosoUniversityContext. O nome do contexto atualizado: ContosoUniversity.Data.SchoolContext
      • Selecione Adicionar para concluir a adição da classe de contexto de dados.
      • Selecione Adicionar para concluir a caixa de diálogo Adicionar Razor Páginas .

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 scaffold.

O processo de scaffolding:

  • Cria Razor páginas na pasta Páginas/Alunos :
    • 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.
  • Adiciona uma cadeia de conexão de banco de dados a appsettings.json.

Cadeia de conexão de banco de dados

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

A cadeia de conexão especifica SQL Server LocalDB:

{
  "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 singular DbSet<Student> Student para o plural DbSet<Student> Students. Para fazer com que o Razor código de Páginas corresponda ao novo DBSet nome, 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 DBSet propriedades sejam plurais.

O código realçado:

  • Cria uma DbSet<TEntity> propriedade 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 ser bloqueado e usado para inicializar o contexto.
    • É necessário porque, posteriormente, no tutorial, a Student entidade 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 os SchoolContext registrados com injeção de dependência durante a inicialização do aplicativo. Componentes que exigem esses serviços, como Razor Páginas, são fornecidos por meio de parâmetros de 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 seguintes linhas realçadas 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 desenvolvimento local, o sistema de configuração ASP.NET Core lê a cadeia de conexão do arquivo ou do appsettings.jsonappsettings.Development.json arquivo.

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

Adicione AddDatabaseDeveloperPageExceptionFilter e UseMigrationsEndPoint , conforme mostrado no código a seguir:

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 Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pacote NuGet fornece ASP.NET Core middleware para páginas de erro do Entity Framework Core. Esse middleware ajuda a detectar e diagnosticar erros com migrações do Entity Framework Core.

Fornece AddDatabaseDeveloperPageExceptionFilter informações de erro úteis no ambiente de desenvolvimento para 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 EnsureCreated método não executará nenhuma ação se houver 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 é excluído pelo qual foi criado EnsureCreated 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 DbInitializer.Initialize linha:
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 para Y excluir o banco de dados.

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

Exibir o banco de dados

  • Abra o SSOX (Pesquisador de Objetos do SQL Server) no menu Exibir do Visual Studio.
  • No SSOX, selecione (localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID}. O nome do banco de dados é gerado a partir do nome de 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 ASP.NET Core

A programação assíncrona é o modo padrão do ASP.NET Core e 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 o código síncrono, muitos threads podem estar vinculados enquanto não estão funcionando 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 arbitrário de linhas. Uma consulta deve usar paginação ou uma abordagem de limitação. Por exemplo, a consulta anterior poderia ser usada 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 em parte por meio da enumeração.

A paginação é abordada posteriormente no tutorial.

Para obter mais informações, consulte considerações de desempenho (EF).

Próximas etapas

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

Este é o primeiro de uma série de tutoriais que mostram como usar o Entity Framework (EF) Core em um aplicativo ASP.NET Core Razor Pages. 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 primeira abordagem de código. Para obter informações sobre como seguir este tutorial usando a primeira abordagem do banco de dados, consulte este problema do Github.

Baixe ou exiba o aplicativo concluído.Baixar instruções.

Pré-requisitos

  • Se você não estiver familiarizado Razorcom Razor o Pages, passe pela série de tutoriais Introdução às Páginas antes de iniciar esta.

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 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.

Página Índice de Alunos

Página Editar Alunos

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

Opcional: compilar o download de exemplo

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

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 ASP.NET Core Aplicativo> WebAvançar.
  3. Na caixa de diálogo Configurar seu novo projeto , insira ContosoUniversity o nome do projeto. É importante usar esse nome exato, incluindo a capitalização, para que cada namespace um corresponda 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. ASP.NET Core Aplicativo Web.
    3. Criar caixa de diálogo Criar Projeto ASP.NET Core

Configurar o estilo do site

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

<!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 Home entradas e Privacy o menu são excluídos.
  • As inscrições são adicionadas para Sobre, Alunos, Cursos, Instrutores e Departamentos.

In 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:

Diagrama de Modelo de Dados Course-Enrollment-Student

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

A entidade Student

Diagrama da entidade Student

  • 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 nomeada ID ou classnameID como a chave primária. Portanto, o nome alternativo reconhecido automaticamente para a chave primária da classe Student é StudentID. Para obter mais informações, consulte 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 registro estará relacionada a uma linha Student se sua StudentID coluna 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

Diagrama da entidade Enrollment

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 o usam 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 ainda não é conhecida ou 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

Diagrama de entidade Curso

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 ASP.NET Core é usada para gerar:

  • Uma classe EF Core DbContext . 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 páginas que lidam com operações CRUD (Criar, Ler, Atualizar e Excluir) para a Student entidade.
  • 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.
  • Na caixa de diálogo Adicionar Novo Item scaffold :
    • Na guia à esquerda, selecione Páginas Comuns >Razor Instaladas >
    • Selecione Razor Páginas usando o CRUD (Entity Framework)>ADD.
  • Na caixa de diálogo Adicionar Razor Páginas usando o CRUD (Entity Framework ):
    • Na lista suspensa classe Modelo, selecione Aluno (ContosoUniversity.Models).
    • Na linha Classe de contexto de dados, selecione o sinal de + (adição).
      • Altere o nome do contexto de dados para terminar em SchoolContext 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 a caixa de diálogo Adicionar Razor Páginas .

Se o scaffolding falhar com o erro 'Install the package Microsoft.VisualStudio.Web.CodeGeneration.Design and try again.', execute a ferramenta scaffold novamente ou veja esse 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 páginas na pasta Páginas/Alunos :
    • 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.
  • Adiciona 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 appsettings.json arquivo.

A cadeia de conexão especifica SQL Server LocalDB:

{
  "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 singular DbSet<Student> Student para o plural DbSet<Student> Students. Para fazer com que o Razor código páginas corresponda ao novo DBSet nome, 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 DBSet propriedades sejam plurais.

O código realçado:

  • Cria uma DbSet<TEntity> propriedade 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 que o modelo tenha sido bloqueado e usado para inicializar o contexto.
    • É necessário porque, posteriormente, no tutorial, a Student entidade 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 os SchoolContext registrados com injeção de dependência durante a inicialização do aplicativo. Componentes que exigem esses serviços, como Razor Páginas, são fornecidos por meio de parâmetros de 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 seguintes linhas realçadas 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 desenvolvimento local, o sistema de configuração ASP.NET Core lê a cadeia de conexão do appsettings.json arquivo.

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

Adicione AddDatabaseDeveloperPageExceptionFilter e UseMigrationsEndPoint , conforme mostrado no código a seguir:

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 Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore pacote NuGet fornece ASP.NET Core middleware para páginas de erro do Entity Framework Core. Esse middleware ajuda a detectar e diagnosticar erros com migrações do Entity Framework Core.

Fornece AddDatabaseDeveloperPageExceptionFilter informações de erro úteis no ambiente de desenvolvimento para 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 EnsureCreated método não executará nenhuma ação se houver 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 é excluído pelo qual foi criado EnsureCreated 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 DbInitializer.Initialize linha:

      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 para Y excluir o banco de dados.

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

Exibir o banco de dados

  • Abra o SSOX (Pesquisador de Objetos do SQL Server) no menu Exibir do Visual Studio.
  • No SSOX, selecione (localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID}. O nome do banco de dados é gerado a partir do nome de 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 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 o código síncrono, muitos threads podem estar vinculados enquanto não estão funcionando 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 arbitrário de linhas. Uma consulta deve usar paginação ou uma abordagem de limitação. Por exemplo, a consulta anterior poderia ser usada 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 em parte por meio da enumeração.

MaxModelBindingCollectionSize o padrão é 1024. Os seguintes conjuntos de códigos 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();
}

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

A paginação é abordada posteriormente no tutorial.

Para obter mais informações, consulte as considerações de desempenho (EF).

Log do SQL do Entity Framework Core

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

{
  "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 ON anterior JS, 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 Log no .NET Core e ASP.NET Core e este problema do GitHub.

Próximas etapas

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

Este é o primeiro de uma série de tutoriais que mostram como usar o Entity Framework (EF) Core em um aplicativo ASP.NET Core Razor Pages. 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 primeira abordagem de código. Para obter informações sobre como seguir este tutorial usando a primeira abordagem do banco de dados, consulte este problema do Github.

Baixe ou exiba o aplicativo concluído.Baixar instruções.

Pré-requisitos

  • Se você não estiver familiarizado Razorcom Razor o Pages, passe pela série de tutoriais Introdução às Páginas antes de iniciar esta.

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 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.

Página Índice de Alunos

Página Editar Alunos

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 do site, o rodapé e o menu atualizando Pages/Shared/_Layout.cshtml:

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

  • Exclua as Home entradas e Privacy o menu e adicione 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>

InPages/Index.cshtml, substitua o conteúdo do arquivo pelo código a seguir para substituir o texto sobre 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:

Diagrama de Modelo de Dados Course-Enrollment-Student

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

A entidade Student

Diagrama da entidade Student

  • 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 nomeada ID ou classnameID como a chave primária. Portanto, o nome alternativo reconhecido automaticamente para a chave primária da classe Student é StudentID. Para obter mais informações, consulte 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

Diagrama da entidade Enrollment

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 ainda não é conhecida ou 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

Diagrama de entidade Curso

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 contexto do 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 páginas que lidam com operações CRUD (Criar, Ler, Atualizar e Excluir) para a Student entidade.
  • 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.
  • Na caixa de diálogo Adicionar Scaffold , selecione Razor Páginas usando o CRUD (Entity Framework)>ADD.
  • Na caixa de diálogo Adicionar Razor Páginas usando o CRUD (Entity Framework ):
    • Na lista suspensa classe Modelo, selecione Aluno (ContosoUniversity.Models).
    • Na linha Classe de contexto de dados, selecione o sinal de + (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 páginas na pasta Páginas/Alunos :
    • 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.
  • Adiciona uma cadeia de conexão de banco de dados a appsettings.json.

Cadeia de conexão de banco de dados

O appsettings.json arquivo 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 realçado cria uma DbSet<TEntity> propriedade 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 Razor código Páginas corresponda ao novo nome 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 (como Razor Páginas) são fornecidos por meio de parâmetros de 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 desenvolvimento local, o sistema de configuração ASP.NET Core lê a cadeia de conexão do appsettings.json arquivo.

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 EnsureCreated método não executará nenhuma ação se houver 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.

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

    // 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.

Exibir o banco de dados

  • Abra o SSOX (Pesquisador de Objetos do SQL Server) no menu Exibir do Visual Studio.
  • No SSOX, selecione (localdb)\MSSQLLocalDB > Databases > 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 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