Partilhar via


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

Por Tom Dykstra e Rick Anderson

Este tutorial ensina ASP.NET Core MVC e Entity Framework Core com controladores e exibições. Razor Pages é um modelo de programação alternativo. Para novos desenvolvimentos, recomendamos Razor Pages em vez de MVC com controladores e views. Consulte a versão Razor Páginas deste tutorial. Cada tutorial cobre algum material que o outro não:

Algumas coisas que este tutorial do MVC tem que o tutorial do Razor Pages não tem:

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

Algumas coisas que o tutorial do Razor Pages tem que este não tem:

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

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

O aplicativo de exemplo é um site da Web para uma Universidade Contoso fictícia. Inclui funcionalidades como admissão de alunos, criação de cursos e tarefas de instrutores. Este é o primeiro de uma série de tutoriais que explicam como criar o aplicativo de exemplo da Universidade Contoso.

Pré-requisitos

  • Visual Studio 2022 com a carga de trabalho de ASP.NET e desenvolvimento web.
  • .NET 6.0 SDK

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

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

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

Mecanismos de banco de dados

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

Solucionar e diagnosticar problemas

Se você se deparar com um problema que não pode resolver, geralmente pode encontrar a solução comparando seu código com o projeto concluído. Para obter uma lista de erros comuns e como resolvê-los, consulte seção Solução de problemas do último tutorial da série. Se você não encontrar o que precisa lá, você pode postar uma pergunta para StackOverflow.com para ASP.NET Core ou EF Core.

Dica

Esta é uma série de 10 tutoriais, cada um dos quais se baseia no que é feito em tutoriais anteriores. Considere salvar uma cópia do projeto após cada conclusão bem-sucedida do tutorial. Então, se você tiver problemas, você pode começar de novo a partir do tutorial anterior em vez de voltar para o início de toda a série.

Aplicativo Web da Universidade Contoso

O aplicativo construído nesses tutoriais é um site básico da universidade.

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

Página Índice de Estudantes

Alunos Editar página

Criar aplicativo Web

  1. Inicie o Visual Studio e selecione Criar um novo projeto.
  2. Na caixa de diálogo Criar um novo projeto, selecione ASP.NET Core Aplicação Web>Seguinte.
  3. Na caixa de diálogo Configurar o seu novo projeto, digite ContosoUniversity para Nome do Projeto. É importante usar esse nome exato, incluindo maiúsculas, para que cada namespace 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 nos menus suspensos.
    2. ASP.NET Core Web App (Model-View-Controller).
    3. caixa de diálogo CriarNovo Projeto ASP.NET Core

Configurar o estilo do site

Algumas alterações básicas configuram o menu do site, o layout e a página inicial.

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

  • Altere cada ocorrência de ContosoUniversity para Contoso University. Há três ocorrências.
  • Adicione entradas de menu para Sobre, Alunos, Cursos, Instrutorese Departamentose elimine a entrada do menu Privacy.

As alterações anteriores são destacadas no código a seguir:

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

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

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

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

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

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

Página inicial da Universidade Contoso

EF Core NuGet pacotes

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

O pacote EF SQL Server e suas dependências, Microsoft.EntityFrameworkCore e Microsoft.EntityFrameworkCore.Relational, fornecem suporte de tempo de execução para EF.

Adicione o pacote NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore. No Console do Gerenciador de Pacotes (PMC), insira os seguintes comandos para adicionar os pacotes NuGet:

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

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

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

Criar o modelo de dados

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

Curso -Enrollment-Student diagrama de modelo de dados

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

  • Uma relação um-para-muitos entre as entidades Student e Enrollment. Um aluno pode estar inscrito em qualquer número de cursos.
  • Uma relação um-para-muitos entre as entidades Course e Enrollment. Um curso pode ter qualquer número de alunos matriculados.

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

A entidade estudantil

Diagrama de entidade estudantil

Na pasta Models, crie a classe 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 é a coluna de chave primária (PK) da tabela de banco de dados que corresponde a essa classe. Por padrão, o EF interpreta uma propriedade chamada ID ou classnameID como a chave primária. Por exemplo, o PK pode ser nomeado StudentID em vez de ID.

A propriedade Enrollments é uma propriedade de navegação . As propriedades de navegação mantêm outras entidades que estão associadas a esta entidade. A propriedade Enrollments de uma entidade Student:

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

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

Se uma propriedade de navegação puder conter várias entidades:

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

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

A entidade de Inscrição

Diagrama de entidade de inscrição

Na pasta Models, crie a classe 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 é PK. Esta entidade usa o padrão classnameID em vez de ID por si só. A entidade Student usou o padrão ID. Alguns desenvolvedores preferem usar um padrão em todo o modelo de dados. Neste tutorial, a variação ilustra que qualquer padrão pode ser usado. Um tutorial posterior mostra como o uso de ID sem classname facilita a implementação de herança no modelo de dados.

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

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

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

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

A entidade do Curso

Diagrama de entidade do curso

Na pasta Models, crie a classe 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 com qualquer número de entidades Enrollment.

O atributo DatabaseGenerated é explicado num tutorial posterior. Este atributo permite inserir a PK para o curso em vez de ter o banco de dados gerá-lo.

Criar o contexto do banco de dados

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

Na pasta do projeto, crie uma pasta chamada Data.

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

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

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

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

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

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

As declarações DbSet<Enrollment> e DbSet<Course> poderiam ser omitidas e funcionaria da mesma forma. A EF inclui-los-ia implicitamente porque:

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

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

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

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

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

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

Registe o SchoolContext

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

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

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

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

        public IConfiguration Configuration { get; }

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

            services.AddControllersWithViews();
        }

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

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

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

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

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

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

    services.AddDatabaseDeveloperPageExceptionFilter();

    services.AddControllersWithViews();
}

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

SQL Server Express LocalDB (uma versão leve do SQL Server para desenvolvimento e testes)

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

Inicializar banco de dados com dados de teste

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

O método EnsureCreated é usado para criar automaticamente o banco de dados. Em um tutorial posterior, você irá ver como lidar com alterações de modelo usando migrações Code First para alterar o esquema de base de dados em vez de eliminar e recriar a base de dados.

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

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

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

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

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

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

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

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

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

Atualize Program.cs com o seguinte código:

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

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

            CreateDbIfNotExists(host);

            host.Run();
        }

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

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

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

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

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

    host.Run();
}

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

  • Exclua o banco de dados.
  • Atualize o método seed e comece de novo com uma nova base de dados.

Em tutoriais posteriores, o banco de dados é modificado quando o modelo de dados é alterado, sem excluí-lo e recriá-lo. Nenhum dado é perdido quando o modelo de dados é alterado.

Criar controlador e vistas

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

A criação automática de CRUD métodos de ação e visualizações é conhecida como andaime.

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

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

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

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

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

ASP.NET Core injeta dependências e cuida de fornecer uma instância de SchoolContext para o controlador. Você configurou isso na classe Startup.

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

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

Os elementos de programação assíncrona neste código são explicados posteriormente no tutorial.

A vista Views/Students/Index.cshtml apresenta esta lista numa tabela:

@model IEnumerable<ContosoUniversity.Models.Student>

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

<h2>Index</h2>

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

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

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

página inicial estreita da Universidade Contoso

Página Índice de Alunos

Ver a base de dados

Quando o aplicativo é iniciado, o método DbInitializer.Initialize chama EnsureCreated. A EF verificou que não existia uma base de dados:

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

Use SQL Server Object Explorer (SSOX) para exibir o banco de dados no Visual Studio:

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

Tabelas no SSOX

Clique com o botão direito do rato na tabela Student e clique em Ver Dados para visualizar os dados na tabela.

Tabela de alunos no SSOX

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

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

  • Faça uma alteração na classe Student.
  • Exclua o banco de dados.
  • Pare e inicie o aplicativo. O banco de dados é recriado automaticamente para corresponder à alteração.

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

Convenções

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

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

O comportamento convencional pode ser anulado. Por exemplo, os nomes de tabela podem ser especificados explicitamente, conforme mostrado anteriormente neste tutorial. Os nomes das colunas e qualquer propriedade podem ser definidos como PK ou FK.

Código assíncrono

A programação assíncrona é o modo padrão para 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 sejam liberados. Com o código síncrono, muitas threads podem ser bloqueadas sem realizar nenhum trabalho porque estão aguardando a conclusão das operações de E/S. Com o código assíncrono, quando um processo está aguardando a conclusão da E/S, seu thread é liberado para o servidor usar para processar outras solicitações. Como resultado, o código assíncrono permite que os recursos do servidor sejam usados de forma mais eficiente e o servidor está habilitado para lidar com mais tráfego sem atrasos.

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

No código a seguir, async, Task<T>, awaite ToListAsync fazem com que o código seja executado assincronamente.

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

Algumas coisas a ter em conta ao escrever código assíncrono que utiliza EF:

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

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

Limitar entidades buscadas

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

Registo SQL do Entity Framework Core

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

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

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

Para obter mais informações, consulte Logging in .NET Core e ASP.NET Core e este problema do GitHub.

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

Este tutorial ensina ASP.NET Core MVC e Entity Framework Core com controladores e exibições. Razor Pages é um modelo de programação alternativo. Para novos desenvolvimentos, recomendamos Razor Páginas em vez de MVC com controladores e vistas. Consulte a versão Razor Pages deste tutorial. Cada tutorial cobre algum material que o outro não:

Algumas coisas que este tutorial do MVC tem que o tutorial do Razor Pages não tem:

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

Algumas coisas que o tutorial do Razor Pages tem que este não tem:

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

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

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

O aplicativo de exemplo é um site da Web para uma Universidade Contoso fictícia. Inclui funcionalidades como admissão de alunos, criação de cursos e tarefas de instrutores. Este é o primeiro de uma série de tutoriais que explicam como criar o aplicativo de exemplo da Universidade Contoso do zero.

Pré-requisitos

  • .NET Core SDK 2.2
  • Visual Studio 2019 com as seguintes cargas de trabalho:
    • ASP.NET e desenvolvimento web carga de trabalho
    • Desenvolvimento multiplataforma .NET Core carga de trabalho

Solução de problemas

Se você se deparar com um problema que não pode resolver, geralmente pode encontrar a solução comparando seu código com o projeto concluído. Para obter uma lista de erros comuns e como resolvê-los, consulte seção Solução de problemas do último tutorial da série. Se você não encontrar o que precisa lá, você pode postar uma pergunta para StackOverflow.com para ASP.NET Core ou EF Core.

Dica

Esta é uma série de 10 tutoriais, cada um dos quais se baseia no que é feito em tutoriais anteriores. Considere salvar uma cópia do projeto após cada conclusão bem-sucedida do tutorial. Então, se você tiver problemas, você pode começar de novo a partir do tutorial anterior em vez de voltar para o início de toda a série.

Aplicativo Web da Universidade Contoso

O aplicativo que você estará construindo nestes tutoriais é um site universitário simples.

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

Página de Índice de Alunos

Alunos Editar a página

Criar aplicativo Web

  • Abra o Visual Studio.

  • No menu Arquivo, selecione Novo > Projeto.

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

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

  • Digite ContosoUniversity como o nome e clique em OK.

    diálogo Novo Projeto

  • Aguarde até que a caixa de diálogo New ASP.NET Core Web Application apareça.

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

  • Certifique-se de que Autenticação está definido para Sem Autenticação.

  • Selecione OK

    caixa de diálogo Novo Projeto do ASP.NET Core

Configurar o estilo do site

Algumas alterações simples configurarão o menu, o layout e a página inicial do site.

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

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

  • Adicione entradas de menu para Sobre, Alunos, Cursos, Instrutorese Departamentose elimine a entrada do menu Privacy.

As mudanças são destacadas.

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

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

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

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

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

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

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

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

Pressione CTRL+F5 para executar o projeto ou escolha Depurar > Iniciar sem Depurar no menu. Você vê a página inicial com guias para as páginas que você criará nestes tutoriais.

Página inicial da Universidade Contoso

Sobre pacotes NuGet EF Core

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

O pacote EF SQL Server e suas dependências (Microsoft.EntityFrameworkCore e Microsoft.EntityFrameworkCore.Relational) fornecem suporte de tempo de execução para EF. Você adicionará um pacote de ferramentas mais tarde, no tutorial Migrations.

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

Criar o modelo de dados

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

Curso -Enrollment-Student diagrama de modelo de dados

Há uma relação um-para-muitos entre entidades Student e Enrollment, e há uma relação um-para-muitos entre entidades Course e Enrollment. Em outras palavras, um aluno pode estar matriculado em qualquer número de cursos, e um curso pode ter qualquer número de alunos matriculados nele.

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

A entidade estudantil

Diagrama de entidade estudantil

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

using System;
using System.Collections.Generic;

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

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

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

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

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

A entidade de Inscrição

Diagrama de entidade de inscrição

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

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

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

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

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

A propriedade Grade é um enum. O ponto de interrogação após a declaração de tipo Grade indica que a propriedade Grade é anulável. Uma nota nula é diferente de uma nota zero -- nula significa que uma nota 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, portanto, a propriedade só pode manter uma única entidade Student (ao contrário da propriedade de navegação Student.Enrollments que você viu anteriormente, que pode conter várias entidades Enrollment).

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

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

Entidade do Curso

Diagrama de entidade do curso

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

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

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

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

A propriedade Enrollments é uma propriedade de navegação. Uma entidade Course pode estar relacionada com qualquer número de entidades Enrollment.

Falaremos mais sobre o atributo DatabaseGenerated num tutorial posterior desta série. Basicamente, esse atributo permite inserir a chave primária para o curso em vez de fazer com que o banco de dados a gere.

Criar o contexto do banco de dados

A classe principal que coordena a funcionalidade do Entity Framework para um determinado modelo de dados é a classe de contexto do banco de dados. Você cria essa classe derivando da classe Microsoft.EntityFrameworkCore.DbContext. Em seu código, você especifica quais entidades são incluídas no modelo de dados. Você também pode personalizar determinado comportamento do Entity Framework. Neste projeto, a classe é chamada SchoolContext.

Na pasta do projeto, crie uma pasta chamada Data.

Na pasta Data crie um novo arquivo de classe chamado SchoolContext.cse substitua o código do modelo pelo seguinte código:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

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

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

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

Você poderia ter omitido as declarações DbSet<Enrollment> e DbSet<Course> e funcionaria da mesma forma. O Entity Framework os incluiria implicitamente porque a entidade Student faz referência à entidade Enrollment e a entidade Enrollment faz referência à entidade Course.

Quando o banco de dados é criado, o EF cria tabelas com nomes iguais aos nomes de propriedade DbSet. Os nomes de propriedade para coleções são tipicamente plurais (Estudantes em vez de Estudante), mas os desenvolvedores discordam quanto a se os nomes das tabelas devem ser plurais ou não. Para estes tutoriais, você substituirá o comportamento padrão especificando nomes de tabela singular no DbContext. Para fazer isso, adicione o seguinte código realçado após a última propriedade DbSet.

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

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

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

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

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

Registar o Contexto Escolar

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

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

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

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

    services.AddMvc();
}

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

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

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

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

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

SQL Server Express LocalDB

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

Inicializar banco de dados com dados de teste

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

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

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

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

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

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

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

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

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

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

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

  • Obtenha uma instância do contexto da base de dados a partir do contentor de injeção de dependência.
  • Chame o método semente, passando para ele o contexto.
  • Descarte o contexto quando o método seed estiver concluído.
using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

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

            CreateDbIfNotExists(host);

            host.Run();
        }

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

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

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

  • Exclua o banco de dados.
  • Atualize o método seed e comece do zero com uma nova base de dados da mesma maneira.

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

Criar controlador e vistas

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

A criação automática de métodos e visualizações de ação CRUD é conhecida como andaime. Scaffolding difere da geração de código porque o código scaffolded é um ponto de partida que tu podes modificar para atender às tuas próprias necessidades, enquanto normalmente não modificas o código gerado. Quando você precisa personalizar o código gerado, você usa classes parciais ou regenera o código quando as coisas mudam.

  • Clique com o botão direito do rato na pasta Controladores de no do Explorador de Soluções e selecione Adicionar Novo Item Andaime .
  • Na caixa de diálogo Adicionar Esqueleto:
    • Selecione o controlador MVC com visualizações, usando o Entity Framework.
    • Clique Adicionar. A caixa de diálogo Adicionar Controlador MVC com visualizações, usando Entity Framework é exibida: Scaffold Student
    • Na classe Modelo , selecione Aluno .
    • Em classe de contexto de dados selecione SchoolContext.
    • Aceite o padrão StudentsController como o nome.
    • Clique Adicionar.

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

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

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

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

A injeção de dependência do ASP.NET Core encarrega-se de passar uma instância de SchoolContext para o controlador. Isso foi configurado no arquivo Startup.cs.

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

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

Você aprenderá sobre os elementos de programação assíncrona neste código posteriormente no tutorial.

A vista Views/Students/Index.cshtml apresenta esta lista numa tabela:

@model IEnumerable<ContosoUniversity.Models.Student>

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

<h2>Index</h2>

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

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

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

Página inicial estreita da Universidade Contoso

Página Índice de Estudantes

Ver a base de dados

Quando você iniciou o aplicativo, o método DbInitializer.Initialize chama EnsureCreated. A EF viu que não havia banco de dados e, portanto, criou um, então o restante do código do método Initialize preencheu o banco de dados com dados. Você pode usar SQL Server Object Explorer (SSOX) para exibir o banco de dados no Visual Studio.

Feche o navegador.

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

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

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

Tabelas no SSOX

Clique com o botão direito do rato na tabela Student e escolha Ver Dados para ver as colunas que foram criadas e as linhas que foram inseridas na tabela.

Tabela de alunos no SSOX

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

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

Convenções

A quantidade de código que você teve que escrever para que o Entity Framework pudesse criar um banco de dados completo para você é mínima devido ao uso de convenções ou suposições que o Entity Framework faz.

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

O comportamento convencional pode ser anulado. Por exemplo, você pode especificar explicitamente nomes de tabelas, como você viu anteriormente neste tutorial. E você pode definir nomes de colunas e definir qualquer propriedade como chave primária ou chave estrangeira, como você verá em um tutorial posterior nesta série.

Código assíncrono

A programação assíncrona é o modo padrão para 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 sejam liberados. Com o código síncrono, muitos threads podem ficar bloqueados enquanto não estão realmente a realizar qualquer trabalho porque estão à espera da conclusão da entrada/saída. Com o código assíncrono, quando um processo está aguardando a conclusão da E/S, seu thread é liberado para o servidor usar para processar outras solicitações. Como resultado, o código assíncrono permite que os recursos do servidor sejam usados de forma mais eficiente e o servidor está habilitado para lidar com mais tráfego sem atrasos.

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

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

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

Algumas coisas a ter em conta quando estiver a escrever código assíncrono que utiliza o Entity Framework:

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

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

Próximos passos

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