Руководство. Начало работы с EF Core веб-приложением MVC ASP.NET

Авторы: Том Дайкстра (Tom Dykstra) и Рик Андерсон (Rick Anderson)

Это руководство описывает MVC-модель ASP.NET Core и Entity Framework Core с контроллерами и представлениями. Razor Pages — это альтернативная модель программирования. Для новой разработки мы рекомендуем использовать Razor Pages, а не MVC с контроллерами и представлениями. См. версию этого руководства для Razor Pages. В каждом руководстве содержатся уникальные материалы:

В руководстве по MVC содержатся материалы, которых нет в руководстве по Razor Pages:

  • Реализация наследования в модели данных
  • Выполнение прямых SQL-запросов
  • Использование динамических запросов LINQ для упрощения кода

В руководстве по Razor Pages содержатся материалы, которых нет в этом руководстве:

  • Использование метода Select для загрузки связанных данных
  • Рекомендации по использованию EF.

На примере веб-приложения для университета Contoso демонстрируется процесс создания веб-приложений ASP.NET Core MVC с помощью Entity Framework (EF) Core и Visual Studio.

В этом примере приложения реализуется веб-сайт вымышленного университета Contoso. На нем предусмотрены различные функции, в том числе прием учащихся, создание курсов и назначение преподавателей. Это первый учебник из серии, в котором описывается создание примера приложения для университета Contoso.

Необходимые компоненты

  • Если у вас нет опыта работы с ASP.NET Core MVC, ознакомьтесь с серией учебников по началу работы с ASP.NET Core MVC, прежде чем приступать к изучению этого учебника.

Это руководство не было обновлено для ASP.NET Core 6 или более поздней версии. Инструкции руководства не будут работать правильно, если вы создаете проект, предназначенный для ASP.NET Core 6 или более поздней версии. Например, веб-шаблоны ASP.NET Core 6 и более поздних версий используют минимальную модель размещения, которая объединяет и Program.cs объединяет Startup.cs один Program.cs файл.

Еще одно различие, введенное в .NET 6, — это функция NRT (ссылочные типы, допускающие значение NULL). Шаблоны проектов позволяют включить эту функцию по умолчанию. Проблемы могут возникать, когда EF рассматривает свойство, необходимое в .NET 6, которое допускает значение NULL в .NET 5. Например, страница create Student не будет автоматически завершатся ошибкой, если Enrollments свойство не имеет значения NULL или asp-validation-summary вспомогательный тег изменяется с ModelOnlyAll.

Мы рекомендуем установить и использовать пакет SDK для .NET 5 для этого руководства. Пока это руководство не будет обновлено, см. статью Razor Pages with Entity Framework Core в ASP.NET Core . Руководство 1 из 8 по использованию Entity Framework с ASP.NET Core 6 или более поздней версии.

Ядра СУБД

В инструкциях для Visual Studio используется SQL Server LocalDB, версия SQL Server Express, которая работает только в Windows.

Решение проблем и устранение неполадок

Если вы столкнулись с проблемами, для их решения можно попробовать сравнить свой код с кодом готового проекта. Список распространенных ошибок и способы их устранения см. в разделе "Устранение неполадок" последнего руководства серии. Если вы не найдете, что вам нужно, вы можете отправить вопрос, чтобы StackOverflow.com для ASP.NET Core или EF Core.

Совет

Эта серия включает в себя 10 учебников, содержание каждого из которых базируется на предыдущих учебниках. После успешного завершения каждого руководства рекомендуется сохранять копию проекта. Таким образом, при возникновении проблем вы сможете вернуться к предыдущему учебнику, а не к началу серии.

Веб-приложение Contoso University

Приложение, создаваемое в этих руководствах, является простым веб-сайтом университета.

Пользователи приложения могут просматривать и обновлять сведения об учащихся, курсах и преподавателях. Вот несколько экранов в приложении:

Students Index page

Students Edit page

Создание веб-приложения

  1. Откройте Visual Studio и выберите Создать проект.
  2. В диалоговом окне Создать проект выберите Веб-приложение ASP.NET Core>Далее.
  3. В диалоговом окне Настроить новый проект введите ContosoUniversity в поле Имя проекта. Очень важно использовать именно такое имя с учетом регистра символов, чтобы пространства имен (namespace) совпадали при копировании кода.
  4. Нажмите кнопку создания.
  5. В диалоговом окне "Создание нового веб-приложения ASP.NET Core" выберите:
    1. В раскрывающихся списках выберите .NET Core и ASP.NET Core 5.0.
    2. Щелкните ASP.NET Core Web App (Model-View-Controller) (Веб-приложение ASP.NET Core (модель — представление — контроллер)).
    3. СозданиеNew ASP.NET Core Project dialog

Настройка стиля сайта

Выполните небольшую базовую настройку меню, макета и домашней страницы сайта.

Откройте Views/Shared/_Layout.cshtml и внесите следующие изменения.

  • Измените каждое вхождение ContosoUniversity на Contoso University. Таких элементов будет три.
  • Добавьте пункты меню About (Сведения), Students (Учащиеся), Courses (Курсы), Instructors (Преподаватели) и Departments (Кафедры). Удалите пункт меню Privacy.

Предыдущие изменения выделены в следующем коде:

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

Замените содержимое файла Views/Home/Index.cshtml следующей разметкой:

@{
    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>

Нажмите клавиши CTRL+F5, чтобы запустить проект, и выберите в меню Отладка > Запуск без отладки. Домашняя страница отображается с вкладками для страниц, созданных при работе с этим учебником.

Contoso University home page

EF Core Пакеты NuGet

В этом учебнике используется SQL Server, для которого требуется пакет поставщика Microsoft.EntityFrameworkCore.SqlServer.

Этот пакет EF SQL Server и его зависимости, Microsoft.EntityFrameworkCore и Microsoft.EntityFrameworkCore.Relational, обеспечивают поддержку среды выполнения для платформы EF.

Добавьте пакет NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore. Введите в консоли диспетчера пакетов (PMC) следующие команды, чтобы добавить пакеты NuGet:

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

Пакет Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore NuGet предоставляет ASP.NET ПО промежуточного слоя Core для EF Core страниц ошибок. Это ПО промежуточного слоя помогает обнаруживать и диагностировать ошибки при EF Core миграции.

Сведения о других поставщиках баз данных, доступных для EF Coreпоставщиков баз данных, см. в разделе "Поставщики баз данных".

Создание модели данных

Для этого приложения будут созданы следующие классы сущностей:

Course-Enrollment-Student data model diagram

У приведенных выше сущностей есть следующие отношения.

  • Между сущностями Student и Enrollment действует связь "один ко многим". Учащегося можно зачислить на любое число курсов.
  • Между сущностями Course и Enrollment действует связь "один ко многим". На курс может быть зачислено любое количество учащихся.

В следующих разделах создается класс для каждой из этих сущностей.

Сущность Student

Student entity diagram

В папке Models создайте класс Student, содержащий следующий код:

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; }
    }
}

Свойство ID используется в качестве столбца первичного ключа (ПК) в таблице базы данных, соответствующей этому классу. По умолчанию платформа EF интерпретирует в качестве первичного ключа свойство ID или classnameID. Например, ПК может называться StudentID, а не ID.

Свойство Enrollments является свойством навигации. Свойства навигации содержат другие сущности, связанные с этой сущностью. Свойство Enrollments сущности Student:

  • Содержит все сущности Enrollment, связанные с этой сущностью Student.
  • Если в Student базе данных есть две связанные Enrollment строки:
    • Свойство навигации Enrollments сущности Student содержит эти две сущности Enrollment.

Строки Enrollment содержат значение первичного ключа учащегося в столбце внешнего ключа (ВК) StudentID.

Если свойство навигации содержит несколько сущностей:

  • Тип должен быть списком, например ICollection<T>, List<T> или HashSet<T>.
  • Сущности можно добавлять, удалять и обновлять.

Связи "многие ко многим" и "один ко многим" могут содержать несколько сущностей. Если используется ICollection<T>, платформа EF по умолчанию создает коллекцию HashSet<T>.

Сущность Enrollment

Enrollment entity diagram

В папке Models создайте класс Enrollment, содержащий следующий код:

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; }
    }
}

Свойство EnrollmentID — это ПК. Эта сущность использует шаблон classnameID вместо самого по себе ID. Сущность Student использовала шаблон ID. Некоторые разработчики предпочитают использовать один шаблон во всей модели данных. В этом учебнике демонстрируется возможность использования любого из шаблонов. В одном из следующих учебников показано, за счет чего использование ID без имени класса позволяет упростить реализацию наследования в модели данных.

Свойство Grade имеет тип enum. Знак ? после объявления типа Grade указывает, что свойство Gradeдопускает значение NULL. Оценка со значением null отличается от нулевой оценки. При значении null оценка еще не известна или не назначена.

Свойство StudentID представляет собой внешний ключ (ВК). Ему соответствует свойство навигации Student. Сущность Enrollment связана с одной сущностью Student, поэтому свойство содержит только отдельную сущность Student. Она отличается от свойства навигации Student.Enrollments, которое содержит несколько сущностей Enrollment.

Свойство CourseID представляет собой ВК. Ему соответствует свойство навигации Course. Сущность Enrollment связана с одной сущностью Course.

Entity Framework интерпретирует свойство как свойство ВК, если оно называется <имя_свойства_навигации><имя_свойства_первичного_ключа>. Например, StudentID для свойства навигации Student, так как сущность Student имеет значение ПК ID. Свойства ВК также могут называться <имя_свойства_первичного_ключа>. Например, CourseID, так как сущность Course имеет значение ПК CourseID.

Сущность Course

Course entity diagram

В папке Models создайте класс Course, содержащий следующий код:

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; }
    }
}

Свойство Enrollments является свойством навигации. Сущность Course может быть связана с любым числом сущностей Enrollment.

Атрибут DatabaseGenerated описан в следующем учебнике. Этот атрибут позволяет ввести ПК для курса, а не использовать базу данных, чтобы создать его.

Создание контекста базы данных

Контекст базы данных DbContext — это основной класс, который координирует функциональные возможности EF для определенной модели данных. Этот класс является производным от класса Microsoft.EntityFrameworkCore.DbContext. Производный класс DbContext указывает сущности, которые включаются в модель данных. Некоторые расширения функциональности EF можно настроить. В этом проекте соответствующий класс называется SchoolContext.

В папке проекта создайте папку с именем Data.

В папке Data создайте файл SchoolContext, содержащий следующий код:

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; }
    }
}

Представленный выше код создает свойство DbSet для каждого набора сущностей. Терминология EF:

  • Набор сущностей обычно соответствует таблице базы данных.
  • Сущность соответствует строке в таблице.

Инструкции DbSet<Enrollment> и DbSet<Course> можно опустить. Это не нарушит функциональность. EF включает их неявно, так как

  • сущность Student ссылается на сущность Enrollment,
  • сущность Enrollment ссылается на сущность Course,

При создании базы данных платформа EF создает таблицы с именами, соответствующими именам свойств в DbSet. Имена свойств для коллекций обычно указаны во множественном числе. Например, используйте идентификатор Students вместо Student. В среде разработчиков нет единого мнения о том, следует ли использовать имена таблиц во множественном числе. В этих учебниках вместо принятого по умолчанию способа таблицам в DbContext присваиваются имена в единственном числе. Чтобы сделать это, добавьте выделенный ниже код после последнего свойства 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");
        }
    }
}

Зарегистрируйте SchoolContext.

ASP.NET Core поддерживает внедрение зависимостей. С помощью внедрения зависимостей службы, например контекст базы данных EF, регистрируются во время запуска приложения. Затем компоненты, которые используют эти службы, например контроллеры MVC, обращаются к ним через параметры конструктора. Код конструктора контроллера, который получает экземпляр контекста, будет приведен позднее в этом учебнике.

Чтобы зарегистрировать SchoolContext как службу, откройте файл Startup.cs и добавьте выделенные строки в метод 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();
        }

Имя строки подключения передается в контекст путем вызова метода для объекта DbContextOptionsBuilder. При локальной разработке система конфигурации ASP.NET Core считывает строку подключения из файла appsettings.json.

Откройте файл appsettings.json и добавьте строку подключения, как показано в следующей разметке:

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

Добавление фильтра исключений базы данных

Добавьте AddDatabaseDeveloperPageExceptionFilter в ConfigureServices, как показано в следующем коде:

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

    services.AddDatabaseDeveloperPageExceptionFilter();

    services.AddControllersWithViews();
}

AddDatabaseDeveloperPageExceptionFilter предоставляет полезные сведения об ошибках в среде разработки.

SQL Server Express LocalDB

Строка подключения указывает базу данных SQL Server LocalDB. LocalDB — это упрощенная версия ядра СУБД SQL Server Express, предназначенная для разработки приложений и не ориентированная на использование в рабочей среде. LocalDB запускается по запросу в пользовательском режиме, поэтому настройки не слишком сложны. По умолчанию LocalDB создает файлы базы данных MDF в каталоге C:/Users/<user>.

Инициализация базы данных с тестовыми данными

EF создает пустую базу данных. В этом разделе добавляется метод, который вызывается после создания базы данных и заполняет ее тестовыми данными.

Метод EnsureCreated будет использоваться для автоматического создания базы данных. В одном из следующих учебников вы узнаете, как обрабатывать изменения модели с использованием Code First Migrations, что позволяет изменять схему базы данных вместо того, чтобы удалять и повторно создавать ее.

В папке Data создайте файл с именем DbInitializer, содержащий следующий код:

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();
        }
    }
}

Предыдущий код проверяет, существует ли база данных:

  • Если база данных не найдена;
    • она создается и загружается вместе с тестовыми данными. Для повышения производительности тестовые данные загружаются массивами, а не коллекциями List<T>.
  • Если база данных найдена, никакие действия не выполняются.

Обновите Program.cs, включив в него следующий код.

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 выполняет следующие действия при запуске приложения:

  • Получение экземпляра контекста базы данных из контейнера внедрения зависимостей.
  • Вызовите метод DbInitializer.Initialize .
  • Удалите контекст по завершении работы метода Initialize, как показано в следующем коде:
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();
}

При первом запуске приложения создается база данных, которая заполняется тестовыми данными. Каждый раз при изменении модели данных:

  • Удалите базу данных.
  • Обновите метод заполнения и запустите заново с новой базой данных.

В последующих учебниках описывается, как изменить базу данных при изменении модели данных, не прибегая к ее удалению и повторному созданию. При изменении модели данных данные не теряются.

Создание контроллера и представлений

Используйте подсистему формирования шаблонов Visual Studio для добавления контроллера и представлений MVC, которые будут использовать платформу EF для запроса данных и их сохранения.

Автоматическое создание методов и представлений операций CRUD (создание, чтение, обновление и удаление) называется формированием шаблонов.

  • В обозревателе решений щелкните правой кнопкой мыши папку Controllers и выберите Добавить > Создать шаблонный элемент.
  • В диалоговом окне "Добавление шаблонов":
    • Выберите Контроллер MVC с представлениями, использующий Entity Framework.
    • Нажмите кнопку Добавить. Появится диалоговое окно "Добавить контроллер MVC с представлениями с помощью Entity Framework" (Добавление контроллера MVC с представлениями с помощью Entity Framework ): Scaffold Student
    • В разделе Класс модели выберите Student.
    • В разделе Класс контекста данных выберите SchoolContext.
    • Оставьте предлагаемое по умолчанию имя StudentsController.
    • Нажмите кнопку Добавить.

Подсистема формирования шаблонов Visual Studio создает файл StudentsController.cs и набор представлений (файлы *.cshtml), которые будут работать с контроллером.

Обратите внимание, что контроллер принимает SchoolContext в качестве параметра конструктора.

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

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

Технология внедрения зависимостей ASP.NET Core обеспечивает передачу экземпляра SchoolContext в контроллер. Вы настраиваете ее в классе Startup.

Контроллер содержит метод действия Index, который отображает всех учащихся в базе данных. Этот метод получает список учащихся из набора сущностей Students, считывая свойство Students экземпляра контекста базы данных:

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

Элементы асинхронного программирования в этом коде рассматриваются далее в этом учебнике.

В Views/Students/Index.cshtml представлении отображается этот список в таблице:

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

Нажмите клавиши CTRL+F5, чтобы запустить проект, и выберите в меню Отладка > Запуск без отладки.

Перейдите на вкладку Students (Учащиеся), чтобы просмотреть тестовые данные, добавленные методом DbInitializer.Initialize. В зависимости от размеров окна браузера ссылку на вкладку Students можно найти вверху страницы или щелкнув значок навигации в правом верхнем углу.

Contoso University home page narrow

Students Index page

Просмотр базы данных

При запуске приложения метод DbInitializer.Initialize вызывает EnsureCreated. Платформа EF определила, что база данных отсутствует,

  • поэтому создала базу данных.
  • Код метода Initialize заполняет базу данных данными.

Для просмотра базы данных в Visual Studio используйте Обозреватель объектов SQL Server (SSOX).

  • Выберите Обозреватель объектов SQL Server из меню Вид в Visual Studio.
  • В SSOX щелкните (localdb)\MSSQLLocalDB > Базы данных.
  • Выберите запись ContosoUniversity1 для имени базы данных, которая находится в строке подключения в файле appsettings.json.
  • Разверните узел Таблицы, чтобы просмотреть представленные в базе таблицы.

Tables in SSOX

Щелкните правой кнопкой мыши таблицу Student и выберите пункт Просмотреть данные, чтобы просмотреть данные в таблице.

Student table in SSOX

Файлы базы данных *.mdf и *.ldf находятся в папке C:\Users\<имя_пользователя>.

Так как EnsureCreated вызывается в методе инициализатора, который выполняется при запуске приложения, можно сделать следующее:

  • Внести изменение в класс Student.
  • Удалите базу данных.
  • Остановить, а затем запустить приложение. База данных автоматически создается повторно в соответствии с изменением.

Например, при добавлении свойства EmailAddress в класс Student во вновь созданной таблице появится новый столбец EmailAddress. В представлении не будет отображаться новое свойство EmailAddress.

Соглашения

Объем кода, написанного для создания полной базы данных платформой EF, сведен к минимуму благодаря использованию соглашений EF.

  • В качестве имен таблиц используются имена свойств DbSet. Для сущностей, на которые не ссылается свойство DbSet, в качестве имен таблиц используются имена классов сущностей.
  • В качестве имен столбцов используются имена свойств сущностей.
  • Свойства сущности с именами ID или classnameID распознаются как свойства ПК.
  • Свойство интерпретируется как свойство ВК, если оно называется <имя_свойства_навигации><имя_свойства_первичного_ключа>. Например, StudentID для свойства навигации Student, так как сущность Student имеет значение ПК ID. Свойства ВК также могут называться <имя_свойства_первичного_ключа>. Например, EnrollmentID, так как сущность Enrollment имеет ПК EnrollmentID.

Стандартное поведение можно переопределить. Например, можно явно задать имена таблиц, как было показано ранее в этом учебнике. Имена столбцов и любое свойство можно задать в качестве ПК или ВК.

Асинхронный код

Асинхронное программирование — это режим по умолчанию для ASP.NET Core и EF Core.

Веб-сервер имеет ограниченное число потоков, поэтому при высокой загрузке могут использоваться все доступные потоки. В таких случаях сервер не может обрабатывать новые запросы до тех пор, пока не будут высвобождены потоки. В синхронном коде многие потоки могут быть заняты, не выполняя при этом какие-либо операции и ожидая завершения ввода-вывода. В асинхронном коде в то время, когда процесс ожидает завершения ввода-вывода, его поток высвобождается и может использоваться сервером для обработки других запросов. Таким образом, асинхронный код позволяет более эффективно использовать ресурсы сервера, который может обрабатывать больше трафика без задержек.

Во время выполнения асинхронный код использует немного больше служебных ресурсов, однако при низком объеме трафика этим можно пренебречь. Тем не менее в случае большого объема трафика это дает существенный выигрыш в производительности.

В следующем коде async, Task<T>, await и ToListAsync обеспечивают асинхронное выполнение кода.

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}
  • Ключевое слово async указывает компилятору создавать обратные вызовы для частей тела метода и автоматически создавать возвращаемый объект Task<IActionResult>.
  • Тип возвращаемого значения Task<IActionResult> представляет текущую операцию с помощью результата типа IActionResult.
  • Ключевое слово await предписывает компилятору разделить метод на две части. Первая часть завершается операцией, которая запускается в асинхронном режиме. Вторая часть помещается в метод обратного вызова, который вызывается при завершении операции.
  • ToListAsync является асинхронной версией метода расширения ToList.

При написании асинхронного кода, который использует EF, нужно учитывать некоторые моменты:

  • Асинхронно выполняются только те инструкции, в результате которых в базу данных отправляются запросы или команды. К ним относятся, например, ToListAsync, SingleOrDefaultAsync и SaveChangesAsync. В их число не входят, например, инструкции, которые просто изменяют IQueryable, такие как var students = context.Students.Where(s => s.LastName == "Davolio").
  • Контекст EF не является потокобезопасным, поэтому не следует пытаться выполнять несколько операций параллельно. При вызове любого асинхронного метода EF всегда используйте ключевое слово await.
  • Чтобы воспользоваться преимуществами в производительности, которые обеспечивает асинхронный код, убедитесь, что все используемые пакеты библиотек также используют асинхронный код при вызове любых методов EF, выполняющих запросы к базе данных.

Дополнительные сведения об асинхронных методах программирования в .NET см. в разделе Обзор асинхронной модели.

Ограничение полученных сущностей

Сведения об ограничении числа сущностей, возвращаемых в запросе, см. в статье Важные замечания о производительности.

Ведение журнала SQL Entity Framework Core

Конфигурация ведения журналов обычно предоставляется разделом Logging в файлах appsettings.{Environment}.json. Чтобы регистрировать инструкции SQL, добавьте "Microsoft.EntityFrameworkCore.Database.Command": "Information" в файл 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": "*"
}

При использовании приведенного выше кода JSON инструкции SQL отображаются в командной строке и в окне вывода Visual Studio.

Дополнительные сведения см. в статье Ведение журнала в ASP.NET Core и описании этой проблемы GitHub.

В следующем учебнике описано, как выполнять основные операции CRUD (создание, чтение, обновление и удаление).

Это руководство описывает MVC-модель ASP.NET Core и Entity Framework Core с контроллерами и представлениями. Razor Pages — это альтернативная модель программирования. Для новой разработки мы рекомендуем использовать Razor Pages, а не MVC с контроллерами и представлениями. См. версию этого руководства для Razor Pages. В каждом руководстве содержатся уникальные материалы:

В руководстве по MVC содержатся материалы, которых нет в руководстве по Razor Pages:

  • Реализация наследования в модели данных
  • Выполнение прямых SQL-запросов
  • Использование динамических запросов LINQ для упрощения кода

В руководстве по Razor Pages содержатся материалы, которых нет в этом руководстве:

  • Использование метода Select для загрузки связанных данных
  • Рекомендации по использованию EF.

Пример веб-приложения Contoso University демонстрирует создание веб-приложений ASP.NET Core 2.2 MVC с помощью Entity Framework (EF) Core 2.2 и Visual Studio 2019.

Это руководство не было обновлено для использования с версией ASP.NET Core 3.1. Оно обновлено для использования с ASP.NET Core 5.0.

В этом примере приложения реализуется веб-сайт вымышленного университета Contoso. На нем предусмотрены различные функции, в том числе прием учащихся, создание курсов и назначение преподавателей. Это первый учебник из серии, в котором с самого начала описывается построение примера приложения для университета Contoso.

Необходимые компоненты

  • Пакет SDK для .NET Core 2.2
  • Visual Studio 2019 со следующими рабочими нагрузками:
    • Рабочая нагрузка ASP.NET и веб-разработка
    • Рабочая нагрузка Кроссплатформенная разработка .NET Core

Устранение неполадок

Если вы столкнулись с проблемами, для их решения можно попробовать сравнить свой код с кодом готового проекта. Список распространенных ошибок и способы их устранения см. в разделе "Устранение неполадок" последнего руководства серии. Если вы не найдете, что вам нужно, вы можете отправить вопрос, чтобы StackOverflow.com для ASP.NET Core или EF Core.

Совет

Эта серия включает в себя 10 учебников, содержание каждого из которых базируется на предыдущих учебниках. После успешного завершения каждого руководства рекомендуется сохранять копию проекта. Таким образом, при возникновении проблем вы сможете вернуться к предыдущему учебнику, а не к началу серии.

Веб-приложение Contoso University

В рамках этих учебников вы будете создавать приложение, которое представляет собой простой веб-сайт университета.

Пользователи приложения могут просматривать и обновлять сведения об учащихся, курсах и преподавателях. Будет создано несколько экранов.

Students Index page

Students Edit page

Создание веб-приложения

  • Откройте Visual Studio.

  • В меню Файл выберите пункт Создать > Проект.

  • В области слева выберите Установленные > Visual C# > Интернет.

  • Выберите шаблон проекта Веб-приложение ASP.NET Core.

  • Введите имя ContosoUniversity и нажмите кнопку ОК.

    New Project dialog

  • Дождитесь появления диалогового окна Создание веб-приложения ASP.NET Core.

  • Выберите .NET Core, ASP.NET Core 2.2 и шаблон Веб-приложение (модель — представление — контроллер).

  • Убедитесь, что для параметра Проверка подлинности задано значение Без проверки подлинности.

  • Выберите ОК

    New ASP.NET Core Project dialog

Настройка стиля сайта

Выполните незначительную настройку меню, макета и домашней страницы сайта.

Откройте Views/Shared/_Layout.cshtml и внесите следующие изменения.

  • Замените все вхождения "ContosoUniversity" на "Contoso University". Таких элементов будет три.

  • Добавьте пункты меню About (Сведения), Students (Учащиеся), Courses (Курсы), Instructors (Преподаватели) и Departments (Кафедры). Удалите пункт меню Privacy.

Изменения выделены.

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

Замените содержимое файла Views/Home/Index.cshtml следующим кодом, который заменяет текст об ASP.NET и MVC на текст о текущем приложении:

@{
    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>

Нажмите клавиши CTRL+F5, чтобы запустить проект, и выберите в меню Отладка > Запуск без отладки. Откроется домашняя страница, на которой будут представлены созданные в рамках этих учебников вкладки.

Contoso University home page

Сведения о EF Core пакетах NuGet

Чтобы добавить EF Core поддержку в проект, установите поставщик базы данных, который вы хотите нацелить. В этом учебнике используется SQL Server, для которого требуется пакет поставщика Microsoft.EntityFrameworkCore.SqlServer. Этот пакет входит в метапакет Microsoft.AspNetCore.App, поэтому ссылаться на него не нужно.

Этот пакет EF SQL Server и его зависимости (Microsoft.EntityFrameworkCore и Microsoft.EntityFrameworkCore.Relational) обеспечивают поддержку среды выполнения для платформы EF. Пакет средств будет добавлен позднее в рамках учебника Миграции.

Дополнительные сведения о других поставщиках баз данных, которые доступны для платформы Entity Framework Core, см. в разделе Поставщики баз данных.

Создание модели данных

Теперь необходимо создать классы сущностей для приложения университета Contoso. Для начала создаются следующие три сущности.

Course-Enrollment-Student data model diagram

Между сущностями Student и Enrollment, а также между сущностями Course и Enrollment существует отношение "один ко многим". Другими словами, учащийся может быть зарегистрирован в любом количестве курсов, а в отдельном курсе может быть зарегистрировано любое количество учащихся.

В следующих разделах создаются классы для каждой из этих сущностей.

Сущность Student

Student entity diagram

В папке Models создайте файл класса с именем Student.cs и замените код шаблона следующим кодом.

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; }
    }
}

Свойство ID будет использоваться в качестве столбца первичного ключа в таблице базы данных, соответствующей этому классу. По умолчанию платформа Entity Framework интерпретирует в качестве первичного ключа свойство ID или classnameID.

Свойство Enrollments является свойством навигации. Свойства навигации содержат другие сущности, связанные с этой сущностью. В этом случае свойство Enrollments сущности Student entity содержит все сущности Enrollment, которые связаны с этой сущностью Student. Другими словами, если со строкой Student в базе данных связаны две строки Enrollment (строки, в столбце внешнего ключа StudentID которых содержится первичный ключ этого учащегося), в этой сущности Student свойство навигации Enrollments будет содержать две этих сущности Enrollment.

Если свойство навигации может содержать несколько сущностей (как в отношениях "многие ко многим" или "один ко многим"), оно должно иметь тип списка, допускающий добавление, удаление и обновление записей, такой как ICollection<T>. Вы можете указать тип ICollection<T> либо, например, тип List<T> или HashSet<T>. Если указан тип ICollection<T>, платформа EF по умолчанию создает коллекцию HashSet<T>.

Сущность Enrollment

Enrollment entity diagram

В папке Models создайте Enrollment.cs и замените существующий код следующим кодом:

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; }
    }
}

Свойство EnrollmentID будет использоваться в качестве первичного ключа. В этой сущности используется шаблон classnameID вместо ID, как в сущности Student. Как правило, следует выбирать один шаблон, который будет использоваться в рамках всей модели данных. В этом случае демонстрируется возможность использования любого из шаблонов. В одном из следующих учебников вы узнаете, за счет чего использование идентификатора без имени класса позволяет упростить реализацию наследования в модели данных.

Свойство Grade имеет тип enum. Знак вопроса после объявления типа Grade указывает, что свойство Grade допускает значение null. Оценка со значением null отличается от нулевой оценки тем, что при таком значении оценка еще не известна или не назначена.

Свойство StudentID представляет собой внешний ключ. Ему соответствует свойство навигации Student. Сущность Enrollment связана с одной сущностью Student, поэтому это свойство может содержать одну сущность Student (в отличие от представленного ранее свойства навигации Student.Enrollments, которое может содержать несколько сущностей Enrollment).

Свойство CourseID представляет собой внешний ключ. Ему соответствует свойство навигации Course. Сущность Enrollment связана с одной сущностью Course.

Платформа Entity Framework интерпретирует свойство как свойство внешнего ключа, если оно имеет имя <navigation property name><primary key property name> (например, StudentID для свойства навигации Student, поскольку сущность Student имеет первичный ключ ID). Свойства внешнего ключа также могут называться просто <primary key property name> (например CourseID, поскольку сущность Course имеет первичный ключ CourseID).

Сущность Course

Course entity diagram

В папке Models создайте Course.cs и замените существующий код следующим кодом:

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; }
    }
}

Свойство Enrollments является свойством навигации. Сущность Course может быть связана с любым числом сущностей Enrollment.

Более подробное описание атрибута DatabaseGenerated будет приведено в одном из следующих учебников этой серии. Фактически, этот атрибут позволяет ввести первичный ключ для курса, а не использовать базу данных, чтобы создать его.

Создание контекста базы данных

Контекст базы данных — это основной класс, который координирует функциональные возможности Entity Framework для заданной модели данных. Этот класс создается путем наследования от класса Microsoft.EntityFrameworkCore.DbContext. В коде указываются сущности, которые включаются в модель данных. Также вы можете настроить реакцию платформы Entity Framework на некоторые события. В этом проекте соответствующий класс называется SchoolContext.

В папке проекта создайте папку Data.

В папке данных создайте файл класса с именем SchoolContext.csи замените код шаблона следующим кодом:

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; }
    }
}

Этот код создает свойство DbSet для каждого набора сущностей. В терминологии Entity Framework набор сущностей обычно соответствует таблице базы данных, а сущность — строке в этой таблице.

Вы можете опустить инструкции DbSet<Enrollment> и DbSet<Course>. Это не нарушит функциональность. Платформа Entity Framework включает эти инструкции неявно, поскольку сущность Student ссылается на сущность Enrollment, а Enrollment ссылается на сущность Course.

При создании базы данных платформа EF создает таблицы с именами, соответствующими именам свойств в DbSet. Имена свойств для коллекций, как правило, задаются во множественном числе (например, Students вместо Student), однако единого мнения по поводу присвоения имен во множественном числе таблицам среди разработчиков не существует. В этих учебниках вместо принятого по умолчанию способа таблицам в DbContext присваиваются имена в единственном числе. Чтобы сделать это, добавьте выделенный ниже код после последнего свойства 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");
        }
    }
}

Выполните сборку проекта, чтобы проверить его на ошибки компиляции.

Регистрация SchoolContext

ASP.NET Core по умолчанию реализует технологию внедрения зависимостей. С помощью внедрения зависимостей службы (например, контекст базы данных EF) регистрируются во время запуска приложения. Затем компоненты, которые используют эти службы (например, контроллеры MVC), обращаются к ним через параметры конструктора. Код конструктора контроллера, который получает экземпляр контекста, будет приведен позднее в этом учебнике.

Чтобы зарегистрировать SchoolContext как службу, откройте файл Startup.cs и добавьте выделенные строки в метод 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();
}

Имя строки подключения передается в контекст путем вызова метода для объекта DbContextOptionsBuilder. При локальной разработке система конфигурации ASP.NET Core считывает строку подключения из файла appsettings.json.

Добавьте инструкции using для пространств имен ContosoUniversity.Data и Microsoft.EntityFrameworkCore, после чего выполните построение проекта.

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

Откройте файл appsettings.json и добавьте строку подключения, как показано в следующем примере.

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

SQL Server Express LocalDB

Строка подключения указывает на базу данных SQL Server LocalDB. LocalDB — это упрощенная версия ядра СУБД SQL Server Express, предназначенная для разработки приложений и не ориентированная на использование в производственной среде. LocalDB запускается по запросу в пользовательском режиме, поэтому настройки не слишком сложны. По умолчанию база данных LocalDB создает файлы .mdf в каталоге C:/Users/<user>.

Инициализация базы данных с тестовыми данными

Платформа Entity Framework создает пустую базу данных. В этом разделе вы напишете метод, который вызывается после создания базы данных и заполняет ее тестовыми данными.

Здесь будет использоваться метод EnsureCreated для автоматического создания базы данных. В одном из следующих учебников вы узнаете, как обрабатывать изменения модели с использованием Code First Migrations, что позволяет изменять схему базы данных вместо того, чтобы удалять и повторно создавать ее.

В папке данных создайте новый файл класса с именем DbInitializer.cs и замените код шаблона следующим кодом, что приводит к созданию базы данных при необходимости и загрузке тестовых данных в новую базу данных.

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();
        }
    }
}

Этот код проверяет, добавлены ли в базу данных учащиеся. Если нет, база данных считается новой и заполняется тестовыми данными. Для повышения производительности тестовые данные загружаются массивами, а не коллекциями List<T>.

В файле Program.cs измените метод Main, чтобы реализовать следующее поведение при запуске приложения:

  • Получение экземпляра контекста базы данных из контейнера внедрения зависимостей.
  • Вызов метода инициализации с передачей ему контекста.
  • Высвобождение контекста после завершения работы метода заполнения.
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>();
                });
    }
}

При первом запуске приложения будет создана и заполнена тестовыми данными необходимая для работы база данных. При каждом изменении модели данных:

  • Удалите базу данных.
  • Обновите метод заполнения и запустите заново с новой базой данных таким же образом.

В следующих учебниках вы узнаете, как изменить базу данных при изменении модели данных, не прибегая к ее удалению и повторному созданию.

Создание контроллера и представлений

В этом разделе подсистема формирования шаблонов Visual Studio используется для добавления контроллера и представлений MVC, которые будут использовать платформу EF для запроса данных и их сохранения.

Автоматическое создание методов и представлений операций CRUD (создание, чтение, обновление и удаление) называется формированием шаблонов. Формирование шаблонов отличается от создания кода тем, что шаблонный код является отправной точкой и может изменяться в соответствии с потребностями, тогда как сформированный код обычно не изменяется. В тех случаях, когда требуется настроить созданный код в соответствии с внесенными изменениями, вы можете использовать разделяемые классы или повторно создать код.

  • Щелкните правой кнопкой мыши папку Контроллеры в обозревателе решений и выберите Добавить > Создать шаблонный элемент.
  • В диалоговом окне "Добавление шаблонов":
    • Выберите Контроллер MVC с представлениями, использующий Entity Framework.
    • Нажмите кнопку Добавить. Появится диалоговое окно "Добавить контроллер MVC с представлениями с помощью Entity Framework" (Добавление контроллера MVC с представлениями с помощью Entity Framework ): Scaffold Student
    • В разделе Класс модели выберите Student.
    • В разделе Класс контекста данных выберите SchoolContext.
    • Оставьте предлагаемое по умолчанию имя StudentsController.
    • Нажмите кнопку Добавить.

Подсистема формирования шаблонов Visual Studio создает файл StudentsController.cs и набор представлений (файлы .cshtml), которые будут работать с контроллером.

Обратите внимание, что контроллер принимает SchoolContext в качестве параметра конструктора.

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

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

Технология внедрения зависимостей ASP.NET Core обеспечивает передачу экземпляра SchoolContext в контроллер. Это было настроено в файле Startup.cs.

Контроллер содержит метод действия Index, который отображает всех учащихся в базе данных. Этот метод получает список учащихся из набора сущностей Students, считывая свойство Students экземпляра контекста базы данных:

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

Позднее в этом учебнике будут описаны элементы асинхронного программирования в этом коде.

В Views/Students/Index.cshtml представлении отображается этот список в таблице:

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

Нажмите клавиши CTRL+F5, чтобы запустить проект, и выберите в меню Отладка > Запуск без отладки.

Перейдите на вкладку Students (Учащиеся), чтобы просмотреть тестовые данные, добавленные методом DbInitializer.Initialize. В зависимости от размеров окна браузера ссылку на вкладку Students можно найти вверху страницы или щелкнув значок навигации в правом верхнем углу.

Contoso University home page narrow

Students Index page

Просмотр базы данных

При запуске приложения метод DbInitializer.Initialize вызывает метод EnsureCreated. Платформа EF определяет, что база данных отсутствует, и создает ее, после чего код в оставшейся части метода Initialize заполняет базу данными. Для просмотра базы данных в Visual Studio можно использовать обозреватель объектов SQL Server (SSOX).

Закройте браузер.

Если окно SSOX не открылось, выберите его в меню Вид Visual Studio.

В окне SSOX щелкните (localdb)\MSSQLLocalDB > Базы данных, а затем щелкните запись базы данных, имя которой указано в строке подключения в вашем файле appsettings.json.

Разверните узел Таблицы, чтобы просмотреть представленные в базе таблицы.

Tables in SSOX

Щелкните правой кнопкой мыши таблицу Student и выберите пункт Просмотр данных, чтобы просмотреть созданные столбцы и строки, вставленные в базу данных.

Student table in SSOX

Файлы базы данных с расширениями MDF и LDF находятся в папке C:\Users\<имя_пользователя>.

Поскольку вы вызываете метод EnsureCreated в методе инициализатора, который выполняется при запуске приложения, теперь вы можете внести изменения в класс Student, удалить базу данных и снова запустить приложение. После этого база данных будет создана повторно в соответствии с внесенными изменениями. Например, при добавлении свойства EmailAddress в класс Student во вновь созданной таблице появится столбец EmailAddress.

Соглашения

Чтобы платформа Entity Framework автоматически создавала полную базу данных на основе принятых соглашений и допущений, потребуется написать минимальный объем кода.

  • В качестве имен таблиц используются имена свойств DbSet. Для сущностей, на которые не ссылается свойство DbSet, в качестве имен таблиц используются имена классов сущностей.
  • В качестве имен столбцов используются имена свойств сущностей.
  • Свойства сущностей с именем ID или classnameID распознаются как свойства первичного ключа.
  • Свойство интерпретируется как свойство внешнего ключа, если оно имеет имя <имя_свойства_навигации><имя_свойства_первичного_ключа> (например StudentID для свойства навигации Student, так как сущность Student имеет первичный ключ ID). Свойства внешнего ключа также могут называться просто <имя_свойства_первичного_ключа> (например EnrollmentID, так как сущность Enrollment имеет первичный ключ EnrollmentID).

Стандартное поведение можно переопределить. Например, можно явно задать имена таблиц, как было показано ранее в этом учебнике. Также можно указать имена столбцов и задать любое свойство в качестве первичного или внешнего ключа, как будет показано в одном из следующих учебников этой серии.

Асинхронный код

Асинхронное программирование — это режим по умолчанию для ASP.NET Core и EF Core.

Веб-сервер имеет ограниченное число потоков, поэтому при высокой загрузке могут использоваться все доступные потоки. В таких случаях сервер не может обрабатывать новые запросы до тех пор, пока не будут высвобождены потоки. В синхронном коде многие потоки могут быть заняты, не выполняя при этом какие-либо операции и ожидая завершения ввода-вывода. В асинхронном коде в то время, когда процесс ожидает завершения ввода-вывода, его поток высвобождается и может использоваться сервером для обработки других запросов. Таким образом, асинхронный код позволяет более эффективно использовать ресурсы сервера, который может обрабатывать больше трафика без задержек.

Во время выполнения асинхронный код использует немного больше служебных ресурсов, однако при низком объеме трафика этим можно пренебречь. Тем не менее в случае большого объема трафика это дает существенный выигрыш в производительности.

В следующем коде для асинхронного выполнения используются ключевое слово async, возвращаемое значение Task<T>, ключевое слово await и метод ToListAsync.

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}
  • Ключевое слово async указывает компилятору создавать обратные вызовы для частей тела метода и автоматически создавать возвращаемый объект Task<IActionResult>.
  • Тип возвращаемого значения Task<IActionResult> представляет текущую операцию с помощью результата типа IActionResult.
  • Ключевое слово await предписывает компилятору разделить метод на две части. Первая часть завершается операцией, которая запускается в асинхронном режиме. Вторая часть помещается в метод обратного вызова, который вызывается при завершении операции.
  • ToListAsync является асинхронной версией метода расширения ToList.

При написании асинхронного кода, который использует Entity Framework, необходимо учитывать некоторые моменты:

  • Асинхронно выполняются только те инструкции, в результате которых в базу данных отправляются запросы или команды. К ним относятся, например, ToListAsync, SingleOrDefaultAsync и SaveChangesAsync. В их число не входят, например, инструкции, которые просто изменяют IQueryable, такие как var students = context.Students.Where(s => s.LastName == "Davolio").
  • Контекст EF не является потокобезопасным, поэтому не следует пытаться выполнять несколько операций параллельно. При вызове любого асинхронного метода EF всегда используйте ключевое слово await.
  • Если вы хотите использовать преимущества в производительности, которые обеспечивает асинхронный код, убедитесь, что все используемые пакеты библиотек (например, для разбиения на страницы) также используют асинхронный код при вызове любых методов Entity Framework, выполняющих запросы к базе данных.

Дополнительные сведения об асинхронных методах программирования в .NET см. в разделе Обзор асинхронной модели.

Следующие шаги

В следующем учебнике описано, как выполнять основные операции CRUD (создание, чтение, обновление и удаление).