Поделиться через


Использование Razor Pages с Entity Framework Core в ASP.NET Core: руководство 1 из 8

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 9 этой статьи.

Авторы: Том Дайкстра (Tom Dykstra), Джереми Ликнесс (Jeremy Likness) и Йон П. Смит (Jon P Smith)

Это первое руководство из серии, посвященной использованию Entity Framework (EF) Core в приложении ASP.NET Core Razor Pages. В учебниках создается веб-сайт для вымышленного университета Contoso. На сайте предусмотрены различные функции, в том числе прием учащихся, создание курсов и назначение преподавателей. В этом руководстве используется подход Code First. См. сведения о работе с этим руководством при использовании подхода Database First в этой проблеме GitHub.

Скачайте или просмотрите готовое приложение. Инструкции по скачиванию.

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

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

Ядра СУБД

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

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

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

Пример приложения

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

Страница указателя учащихся

Страница редактирования учащихся

Стиль пользовательского интерфейса для этого сайта основан на встроенных шаблонах проектов. В этом руководстве основное внимание уделяется использованию EF Core ASP.NET Core, а не настройке пользовательского интерфейса.

Необязательно: сборка примера для скачивания

Этот шаг необязательный. Создание готового приложения рекомендуется в случае, если возникли проблемы, которые не удается решить. Если вы столкнулись с проблемой, которую не можете решить, сравните свой код с кодом готового проекта. Указания по скачиванию.

Выберите ContosoUniversity.csproj, чтобы открыть проект.

  • Выполните сборку проекта.

  • В консоли диспетчера пакетов (PMC) выполните следующую команду:

    Update-Database
    

Запустите проект, чтобы заполнить базу данных.

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

  1. Запустите Visual Studio 2022 и нажмите Создать проект.

    Создание проекта в начальном окне

  2. В диалоговом окне Создать проект выберите Веб-приложение ASP.NET Core и нажмите Далее.

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

  3. В диалоговом окне Настроить новый проект введите ContosoUniversity в поле Имя проекта. Важно присвоить проекту имя ContosoUniversity, включая сопоставление регистра букв, чтобы пространство имени соответствовало при копировании и вставке примера кода.

  4. Выберите Далее.

  5. В диалоговом окне Дополнительные сведения выберите .NET 6.0 (долгосрочная поддержка) и щелкните Создать.

    Дополнительная информация:

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

Скопируйте и вставьте в файл Pages/Shared/_Layout.cshtml следующий код:

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

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

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

Файл макета задает заголовок, нижний колонтитул и меню для сайта. Приведенный выше код вносит следующие изменения:

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

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

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

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

Приведенный выше код заменяет текст об ASP.NET Core текстом об этом приложении.

Запустите приложение, чтобы убедиться, что home откроется страница.

Модель данных

В следующих разделах создается модель данных.

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

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

Сущность Student

Схема сущности Student

  • Создайте папку Models (Модели) в папке проекта.
  • Создайте Models/Student.cs, используя следующий код:
    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 Core интерпретирует свойство, которое называется ID или classnameID является первичным ключом. Поэтому альтернативным автоматически распознаваемым именем для первичного ключа класса Student является StudentID. Дополнительные сведения см. в разделе EF Core "Ключи".

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

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

Свойство Enrollments определено как ICollection<Enrollment>, так как может быть несколько связанных сущностей Enrollment. Можно использовать и другие типы коллекций, например List<Enrollment> или HashSet<Enrollment>. При ICollection<Enrollment> использовании EF Core создается HashSet<Enrollment> коллекция по умолчанию.

Сущность Enrollment

Схема сущности Enrollment

Создайте Models/Enrollment.cs, используя следующий код:

using System.ComponentModel.DataAnnotations;

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

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

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

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

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

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

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

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

Сущность Course

Схема сущности Course

Создайте Models/Course.cs, используя следующий код:

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 позволяет приложению указать первичный ключ, а не использовать созданный базой данных.

сборка приложения. Компилятор создает несколько предупреждений о том, как обрабатываются значения null. Дополнительные сведения см. в сведениях об этой проблеме GitHub, а также в статьях Ссылочные типы, допускающие значение NULL и Руководство по более четкому выражению проектного замысла с помощью ссылочных типов, допускающих и не допускающих значение NULL.

Чтобы исключить предупреждения из ссылочных типов, допускающих значения NULL, удалите следующую строку из файла ContosoUniversity.csproj:

<Nullable>enable</Nullable>

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

Удалите заметку о ссылочном типе ?, допускающем значение NULL, из public string? RequestId { get; set; } в файле Pages/Error.cshtml.cs, чтобы сборка проекта выполнялась без предупреждений компилятора.

Формирование шаблона для страниц Student

В этом разделе для создания указанных ниже компонентов используется средство формирования шаблонов ASP.NET Core.

  • Класс EF CoreDbContext. Контекст —это основной класс, который координирует функциональные возможности Entity Framework для определенной модели данных. Он является производным от класса Microsoft.EntityFrameworkCore.DbContext.
  • Razor Pages с поддержкой операций создания, чтения, обновления и удаления (CRUD) для сущности Student.
  • Создайте папку Pages/Students.
  • В обозревателе решений щелкните правой кнопкой мыши папку Pages/Students и выберите пункты Добавить>Создать шаблонный элемент.
  • В диалоговом окне добавления нового шаблона элемента:
    • На вкладке слева выберите Установленные > Общие >Razor Pages
    • Выберите Razor Pages на основе Entity Framework (CRUD)>Добавить.
  • В диалоговом окне добавления Razor страниц с помощью Entity Framework (CRUD):
    • В раскрывающемся списке Класс модели выберите Student (ContosoUniversity.Models).
    • В строке Класс контекста данных щелкните знак плюса (+).
      • Измените имя контекста данных так, чтобы оно заканчивалось на SchoolContext, а не на ContosoUniversityContext. Новое имя контекста: ContosoUniversity.Data.SchoolContext.
      • Выберите Добавить, чтобы завершить добавление класса контекста данных.
      • Выберите Добавить, чтобы закрыть диалоговое окно Добавление Razor Pages.

Следующие пакеты устанавливаются автоматически:

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

Если предыдущий шаг завершился сбоем, выполните сборку проекта и повторите шаг формирования шаблона.

В процессе формирования шаблона выполняются следующие действия:

  • Создает Razor страницы в папке Pages/Students :
    • Create.cshtml и Create.cshtml.cs
    • Delete.cshtml и Delete.cshtml.cs
    • Details.cshtml и Details.cshtml.cs
    • Edit.cshtml и Edit.cshtml.cs
    • Index.cshtml и Index.cshtml.cs
  • Создает Data/SchoolContext.cs.
  • Добавляет контекст в внедрение Program.csзависимостей.
  • добавляет строку подключения к базе данных в файл appsettings.json.

Строка подключения к базе данных

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

Строка подключения указывает базу данных SQL Server LocalDB.

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

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

Обновление класса контекста базы данных

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

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

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

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

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

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

Приведенный выше код изменяет форму слова DbSet<Student> Student на множественную: DbSet<Student> Students. Чтобы код Razor Pages соответствовал новому DBSet имени, внесите глобальное изменение: _context.Student. на _context.Students..

Всего это имя встречается 8 раз.

Так как набор сущностей содержит несколько сущностей, многие разработчики предпочитают имена свойств DBSet во множественном числе.

Выделенный код:

  • Создает свойство DbSet<TEntity> для каждого набора сущностей. В EF Core терминологии:
    • Набор сущностей обычно соответствует таблице базы данных.
    • Сущность соответствует строке в таблице.
  • Вызывает OnModelCreating. OnModelCreating:
    • Вызывается при SchoolContext инициализации, но до защиты модели и используется для инициализации контекста.
    • является обязательным, так как далее в руководстве сущность Student будет содержать ссылки на другие сущности.

Мы планируем исправить эту проблему в будущем выпуске.

Program.cs

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

Средство формирования шаблонов автоматически зарегистрировало класс контекста в контейнере внедрения зависимостей.

Следующие выделенные строки были добавлены средством формирования шаблонов:

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

builder.Services.AddRazorPages();

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

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

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

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

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

builder.Services.AddRazorPages();

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

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

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

Добавьте пакет NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.

Введите в консоли диспетчера пакетов следующие команды, чтобы добавить пакет NuGet:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

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

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

Создание базы данных

Обновите Program.cs , чтобы создать базу данных, если она не существует:

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

builder.Services.AddRazorPages();

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

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

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

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

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

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

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Метод EnsureCreated не принимает никаких действий, если база данных для контекста существует. Если база данных не существует, она создается вместе со схемой. EnsureCreated обеспечивает описанный ниже рабочий процесс для обработки изменений модели данных.

  • Удалите базу данных. Все существующие данные теряются.
  • Модель данных изменяется. Например, добавляется поле EmailAddress.
  • Выполнить приложение.
  • Метод EnsureCreated создает базу данных с новой схемой.

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

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

Тестирование приложения

  • Выполнить приложение.
  • Щелкните ссылку Students и выберите Создать.
  • Протестируйте ссылки Edit, Details и Delete.

Заполнение базы данных

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

Создайте Data/DbInitializer.cs, используя следующий код:

using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

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

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

  • В Program.cs удалите // из строки DbInitializer.Initialize:
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
}
  • Завершите работу приложения, если оно запущено, и выполните следующую команду в консоли диспетчера пакетов (PMC):

    Drop-Database -Confirm
    
    
  • Выберите Y, чтобы удалить базу данных.

  • Перезапустите приложение.
  • Выберите страницу учащихся, чтобы увидеть заполненные данные.

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

  • Откройте обозреватель объектов SQL Server (SSOX) из меню Вид в Visual Studio.
  • В SSOX щелкните (localdb)\MSSQLLocalDB > Базы данных > SchoolContext-{GUID}. Имя базы данных создается на основе имени контекста, которое вы указали ранее, а также включает дефис и GUID.
  • Разверните узел Таблицы.
  • Щелкните правой кнопкой мыши таблицу Student (Учащийся) и выберите пункт Просмотр данных, чтобы просмотреть созданные столбцы и строки, вставленные в таблицу.
  • Щелкните правой кнопкой мыши таблицу Student (Учащийся) и выберите пункт Просмотреть код, чтобы увидеть, как модель Student соотносится со схемой таблицы Student.

Асинхронные методы EF в веб-приложениях ASP.NET Core

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

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

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

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

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

Некоторые моменты, которые следует учитывать при написании асинхронного кода, использующего EF Core:

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

Дополнительные сведения об асинхронном программировании см. в разделах Обзор асинхронной модели и Асинхронное программирование с использованием ключевых слов Async и Await.

Предупреждение

Асинхронная реализация Microsoft.Data.SqlClient имеет некоторые известные проблемы (#593, #601 и другие). Если возникают непредвиденные проблемы с производительностью, попробуйте использовать выполнение команды синхронизации, особенно при работе с большим текстом или двоичными значениями.

Замечания, связанные с быстродействием

Как правило, веб-страница не должна загружать произвольное число строк. Запрос должен использовать разбиение на страницы или применять ограничение. Например, предыдущий запрос может использовать Take для ограничения количества возвращаемых строк:

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

Если в процессе перечисления большой таблицы в представлении возникло исключение базы данных, может быть возвращен ответ HTTP 200 с сообщением о частично сформированных данных.

Разбиение на страницы рассматривается далее в этом руководстве.

Дополнительные сведения см. в разделе Особенности производительности (EF).

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

Использование SQLite для разработки, SQL Server для рабочей среды

Это первое руководство из серии, посвященной использованию Entity Framework (EF) Core в приложении ASP.NET Core Razor Pages. В учебниках создается веб-сайт для вымышленного университета Contoso. На сайте предусмотрены различные функции, в том числе прием учащихся, создание курсов и назначение преподавателей. В этом руководстве используется подход Code First. См. сведения о работе с этим руководством при использовании подхода Database First в этой проблеме GitHub.

Скачайте или просмотрите готовое приложение. Инструкции по скачиванию.

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

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

Ядра СУБД

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

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

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

Пример приложения

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

Страница указателя учащихся

Страница редактирования учащихся

Стиль пользовательского интерфейса для этого сайта основан на встроенных шаблонах проектов. В этом руководстве основное внимание уделяется использованию EF Core ASP.NET Core, а не настройке пользовательского интерфейса.

Необязательно: сборка примера для скачивания

Этот шаг необязательный. Создание готового приложения рекомендуется в случае, если возникли проблемы, которые не удается решить. Если вы столкнулись с проблемой, которую не можете решить, сравните свой код с кодом готового проекта. Указания по скачиванию.

Выберите ContosoUniversity.csproj, чтобы открыть проект.

  • Выполните сборку проекта.
  • В консоли диспетчера пакетов (PMC) выполните следующую команду:
Update-Database

Запустите проект, чтобы заполнить базу данных.

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

  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.
    3. СозданиеДиалоговое окно

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

Скопируйте и вставьте в файл Pages/Shared/_Layout.cshtml следующий код:

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

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

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

Файл макета задает заголовок, нижний колонтитул и меню для сайта. Приведенный выше код вносит следующие изменения:

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

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

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

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

Приведенный выше код заменяет текст об ASP.NET Core текстом об этом приложении.

Запустите приложение, чтобы убедиться, что home откроется страница.

Модель данных

В следующих разделах создается модель данных.

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

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

Сущность Student

Схема сущности Student

  • Создайте папку Models (Модели) в папке проекта.

  • Создайте 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 используется в качестве столбца первичного ключа в таблице базы данных, соответствующей этому классу. По умолчанию EF Core интерпретирует свойство, которое называется ID или classnameID является первичным ключом. Поэтому альтернативным автоматически распознаваемым именем для первичного ключа класса Student является StudentID. Дополнительные сведения см. в разделе EF Core "Ключи".

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

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

Свойство Enrollments определено как ICollection<Enrollment>, так как может быть несколько связанных сущностей Enrollment. Можно использовать и другие типы коллекций, например List<Enrollment> или HashSet<Enrollment>. При ICollection<Enrollment> использовании EF Core создается HashSet<Enrollment> коллекция по умолчанию.

Сущность Enrollment

Схема сущности Enrollment

Создайте Models/Enrollment.cs, используя следующий код:

using System.ComponentModel.DataAnnotations;

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

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

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

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

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

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

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

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

Сущность Course

Схема сущности Course

Создайте 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 позволяет приложению указать первичный ключ, а не использовать созданный базой данных.

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

Формирование шаблона для страниц Student

В этом разделе для создания указанных ниже компонентов используется средство формирования шаблонов ASP.NET Core.

  • Класс EF CoreDbContext. Контекст —это основной класс, который координирует функциональные возможности Entity Framework для определенной модели данных. Он является производным от класса Microsoft.EntityFrameworkCore.DbContext.
  • Razor Pages с поддержкой операций создания, чтения, обновления и удаления (CRUD) для сущности Student.
  • Создайте папку Pages/Students.
  • В обозревателе решений щелкните правой кнопкой мыши папку Pages/Students и выберите пункты Добавить>Создать шаблонный элемент.
  • В диалоговом окне добавления нового шаблона элемента:
    • На вкладке слева выберите Установленные > Общие >Razor Pages
    • Выберите Razor Pages на основе Entity Framework (CRUD)>Добавить.
  • В диалоговом окне добавления Razor страниц с помощью Entity Framework (CRUD):
    • В раскрывающемся списке Класс модели выберите Student (ContosoUniversity.Models).
    • В строке Класс контекста данных щелкните знак плюса (+).
      • Измените имя контекста данных так, чтобы оно заканчивалось на SchoolContext, а не на ContosoUniversityContext. Новое имя контекста: ContosoUniversity.Data.SchoolContext.
      • Выберите Добавить, чтобы завершить добавление класса контекста данных.
      • Выберите Добавить, чтобы закрыть диалоговое окно Добавление Razor Pages.

Если формирование шаблонов завершится ошибкой 'Install the package Microsoft.VisualStudio.Web.CodeGeneration.Design and try again.', снова запустите средство формирования шаблонов или обратитесь к описанию этой проблемы GitHub.

Следующие пакеты устанавливаются автоматически:

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

Если предыдущий шаг завершился сбоем, выполните сборку проекта и повторите шаг формирования шаблона.

В процессе формирования шаблона выполняются следующие действия:

  • Создает Razor страницы в папке Pages/Students :
    • Create.cshtml и Create.cshtml.cs
    • Delete.cshtml и Delete.cshtml.cs
    • Details.cshtml и Details.cshtml.cs
    • Edit.cshtml и Edit.cshtml.cs
    • Index.cshtml и Index.cshtml.cs
  • Создает Data/SchoolContext.cs.
  • Добавляет контекст в внедрение Startup.csзависимостей.
  • добавляет строку подключения к базе данных в файл appsettings.json.

Строка подключения к базе данных

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

Строка подключения указывает базу данных SQL Server LocalDB.

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

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

Обновление класса контекста базы данных

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

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

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

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

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

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

Приведенный выше код изменяет форму слова DbSet<Student> Student на множественную: DbSet<Student> Students. Чтобы код Razor Pages соответствовал новому DBSet имени, внесите глобальное изменение: _context.Student. на _context.Students..

Всего это имя встречается 8 раз.

Так как набор сущностей содержит несколько сущностей, многие разработчики предпочитают имена свойств DBSet во множественном числе.

Выделенный код:

  • Создает свойство DbSet<TEntity> для каждого набора сущностей. В EF Core терминологии:
    • Набор сущностей обычно соответствует таблице базы данных.
    • Сущность соответствует строке в таблице.
  • Вызывает OnModelCreating. OnModelCreating:
    • Вызывается при SchoolContext инициализации, но до защиты модели и используется для инициализации контекста.
    • является обязательным, так как далее в руководстве сущность Student будет содержать ссылки на другие сущности.

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

Startup.cs

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

Средство формирования шаблонов автоматически зарегистрировало класс контекста в контейнере внедрения зависимостей.

Следующие выделенные строки были добавлены средством формирования шаблонов:

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

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

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

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

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

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

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

    services.AddDatabaseDeveloperPageExceptionFilter();
}

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

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

    app.UseRouting();

    app.UseAuthorization();

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

Добавьте пакет NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.

Введите в консоли диспетчера пакетов следующие команды, чтобы добавить пакет NuGet:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

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

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

Создание базы данных

Обновите 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>();
                    context.Database.EnsureCreated();
                    // DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

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

Метод EnsureCreated не принимает никаких действий, если база данных для контекста существует. Если база данных не существует, она создается вместе со схемой. EnsureCreated обеспечивает описанный ниже рабочий процесс для обработки изменений модели данных.

  • Удалите базу данных. Все существующие данные теряются.
  • Модель данных изменяется. Например, добавляется поле EmailAddress.
  • Выполнить приложение.
  • Метод EnsureCreated создает базу данных с новой схемой.

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

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

Тестирование приложения

  • Выполнить приложение.
  • Щелкните ссылку Students и выберите Создать.
  • Протестируйте ссылки Edit, Details и Delete.

Заполнение базы данных

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

Создайте Data/DbInitializer.cs, используя следующий код:

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

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

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

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

  • В Program.cs удалите // из строки DbInitializer.Initialize:

      context.Database.EnsureCreated();
      DbInitializer.Initialize(context);
    
  • Завершите работу приложения, если оно запущено, и выполните следующую команду в консоли диспетчера пакетов (PMC):

    Drop-Database -Confirm
    
    
  • Выберите Y, чтобы удалить базу данных.

  • Перезапустите приложение.
  • Выберите страницу учащихся, чтобы увидеть заполненные данные.

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

  • Откройте обозреватель объектов SQL Server (SSOX) из меню Вид в Visual Studio.
  • В SSOX щелкните (localdb)\MSSQLLocalDB > Базы данных > SchoolContext-{GUID}. Имя базы данных создается на основе имени контекста, которое вы указали ранее, а также включает дефис и GUID.
  • Разверните узел Таблицы.
  • Щелкните правой кнопкой мыши таблицу Student (Учащийся) и выберите пункт Просмотр данных, чтобы просмотреть созданные столбцы и строки, вставленные в таблицу.
  • Щелкните правой кнопкой мыши таблицу Student (Учащийся) и выберите пункт Просмотреть код, чтобы увидеть, как модель Student соотносится со схемой таблицы Student.

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

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

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

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

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

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

Некоторые моменты, которые следует учитывать при написании асинхронного кода, использующего EF Core:

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

Дополнительные сведения об асинхронном программировании см. в разделах Обзор асинхронной модели и Асинхронное программирование с использованием ключевых слов Async и Await.

Замечания, связанные с быстродействием

Как правило, веб-страница не должна загружать произвольное число строк. Запрос должен использовать разбиение на страницы или применять ограничение. Например, предыдущий запрос может использовать Take для ограничения количества возвращаемых строк:

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

Если в процессе перечисления большой таблицы в представлении возникло исключение базы данных, может быть возвращен ответ HTTP 200 с сообщением о частично сформированных данных.

Значение MaxModelBindingCollectionSize по умолчанию равно 1024. Следующий код задает MaxModelBindingCollectionSize:

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

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

    services.AddRazorPages();

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

    services.AddDatabaseDeveloperPageExceptionFilter();
}

Сведения о параметрах конфигурации, таких как MyMaxModelBindingCollectionSize, см. в статье Конфигурация.

Разбиение на страницы рассматривается далее в этом руководстве.

Дополнительные сведения см. в разделе Особенности производительности (EF).

Ведение журнала 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.

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

Использование SQLite для разработки, SQL Server для рабочей среды

Это первое руководство из серии, посвященной использованию Entity Framework (EF) Core в приложении ASP.NET Core Razor Pages. В учебниках создается веб-сайт для вымышленного университета Contoso. На сайте предусмотрены различные функции, в том числе прием учащихся, создание курсов и назначение преподавателей. В этом руководстве используется подход Code First. См. сведения о работе с этим руководством при использовании подхода Database First в этой проблеме GitHub.

Скачайте или просмотрите готовое приложение. Инструкции по скачиванию.

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

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

Ядра СУБД

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

В инструкциях для Visual Studio Code используется SQLite, кроссплатформенное ядро СУБД.

Если вы решили использовать SQLite, скачайте и установите стороннее средство для управления базой данных SQLite и ее просмотра, например обозреватель базы данных для SQLite.

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

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

Пример приложения

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

Страница указателя учащихся

Страница редактирования учащихся

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

Чтобы получить исходный код готового проекта, перейдите по ссылке в верхней части страницы. В папке cu30 содержится код для версии учебника по ASP.NET Core 3.0. Файлы, отражающие состояние кода для учебников 1–7, находятся в папке cu30snapshots.

Чтобы запустить приложение после скачивания готового проекта, выполните указанные ниже действия.

  • Выполните сборку проекта.

  • В консоли диспетчера пакетов (PMC) выполните следующую команду:

    Update-Database
    
  • Запустите проект, чтобы заполнить базу данных.

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

  • В меню Файл Visual Studio откройте меню Создать>Проект.
  • Выберите Веб-приложение ASP.NET Core.
  • Назовите проект ContosoUniversity. Очень важно использовать именно такое имя с учетом регистра символов, чтобы пространства имен совпадали при копировании и вставке кода.
  • Выберите в раскрывающихся списках пункты .NET Core и ASP.NET Core 3.0, а затем выберите Веб-приложение.

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

Настройте верхний колонтитул сайта и меню, обновив:Pages/Shared/_Layout.cshtml

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

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

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

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

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

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

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

Замените Pages/Index.cshtmlсодержимое файла следующим кодом, чтобы заменить текст о ASP.NET Core текстом об этом приложении:

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

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

Запустите приложение, чтобы убедиться, что home откроется страница.

Модель данных

В следующих разделах создается модель данных.

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

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

Сущность Student

Схема сущности Student

  • Создайте папку Models (Модели) в папке проекта.

  • Создайте 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 используется в качестве столбца первичного ключа в таблице базы данных, соответствующей этому классу. По умолчанию EF Core интерпретирует свойство, которое называется ID или classnameID является первичным ключом. Поэтому альтернативным автоматически распознаваемым именем для первичного ключа класса Student является StudentID. Дополнительные сведения см. в разделе EF Core "Ключи".

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

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

Свойство Enrollments определено как ICollection<Enrollment>, так как может быть несколько связанных сущностей Enrollment. Можно использовать и другие типы коллекций, например List<Enrollment> или HashSet<Enrollment>. При ICollection<Enrollment> использовании EF Core создается HashSet<Enrollment> коллекция по умолчанию.

Сущность Enrollment

Схема сущности Enrollment

Создайте 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. Для рабочей модели данных выберите один шаблон и используйте только его. В этом учебнике используются оба шаблона, чтобы проиллюстрировать их работу. Использование ID без classname упрощает внесение некоторых изменений в модель данных.

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

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

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

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

Сущность Course

Схема сущности Course

Создайте 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 позволяет приложению указать первичный ключ, а не использовать созданный базой данных.

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

Формирование шаблона для страниц Student

В этом разделе вы используете средство формирования шаблонов ASP.NET Core для создания указанных ниже компонентов.

  • Класс контекстаEF Core. Контекст —это основной класс, который координирует функциональные возможности Entity Framework для определенной модели данных. Он является производным от класса Microsoft.EntityFrameworkCore.DbContext.
  • Razor Pages с поддержкой операций создания, чтения, обновления и удаления (CRUD) для сущности Student.
  • В папке Pages создайте папку Students.
  • В обозревателе решений щелкните правой кнопкой мыши папку Pages/Students и выберите пункты Добавить>Создать шаблонный элемент.
  • В диалоговом окне Добавление шаблона щелкните Razor Pages на основе Entity Framework (CRUD)>Добавить.
  • В диалоговом окне добавления Razor страниц с помощью Entity Framework (CRUD):
    • В раскрывающемся списке Класс модели выберите Student (ContosoUniversity.Models).
    • В строке Класс контекста данных щелкните знак плюса (+).
    • Измените имя контекста данных с ContosoUniversity.Models.ContosoUniversityContext на ContosoUniversity.Data.SchoolContext.
    • Выберите Добавить.

Следующие пакеты устанавливаются автоматически:

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

Если в предыдущем шаге возникает проблема, выполните сборку проекта и повторите шаг формирования шаблона.

В процессе формирования шаблона выполняются следующие действия:

  • Создает Razor страницы в папке Pages/Students :
    • Create.cshtml и Create.cshtml.cs
    • Delete.cshtml и Delete.cshtml.cs
    • Details.cshtml и Details.cshtml.cs
    • Edit.cshtml и Edit.cshtml.cs
    • Index.cshtml и Index.cshtml.cs
  • Создает Data/SchoolContext.cs.
  • Добавляет контекст в внедрение Startup.csзависимостей.
  • добавляет строку подключения к базе данных в файл appsettings.json.

Строка подключения к базе данных

В файле appsettings.json указывается строка подключения для SQL Server LocalDB.

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

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

Обновление класса контекста базы данных

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

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

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

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

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

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

Выделенный код создает свойство DbSet<TEntity> для каждого набора сущностей. В EF Core терминологии:

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

Так как набор сущностей содержит несколько сущностей, свойства DBSet должны иметь имена во множественном числе. Так как средство формирования шаблонов создало DBSet Student, в этом шаге его имя меняется на имя во множественном числе: Students.

Чтобы код Razor Pages соответствовал новому имени DBSet, измените _context.Student на _context.Students во всем проекте. Всего это имя встречается 8 раз.

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

Startup.cs

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

Средство формирования шаблонов автоматически зарегистрировало класс контекста в контейнере внедрения зависимостей.

  • Выделенные строки в ConfigureServices были добавлены средством формирования шаблонов:

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

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

Создание базы данных

Обновите 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>();
                    context.Database.EnsureCreated();
                    // DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

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

Метод EnsureCreated не принимает никаких действий, если база данных для контекста существует. Если база данных не существует, она создается вместе со схемой. EnsureCreated обеспечивает описанный ниже рабочий процесс для обработки изменений модели данных.

  • Удалите базу данных. Все существующие данные теряются.
  • Модель данных изменяется. Например, добавляется поле EmailAddress.
  • Выполнить приложение.
  • Метод EnsureCreated создает базу данных с новой схемой.

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

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

Тестирование приложения

  • Выполнить приложение.
  • Щелкните ссылку Students и выберите Создать.
  • Протестируйте ссылки Edit, Details и Delete.

Заполнение базы данных

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

Создайте Data/DbInitializer.cs, используя следующий код:

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

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

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

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

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

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

  • В Program.cs, замените EnsureCreated вызов вызовом DbInitializer.Initialize :

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

Завершите работу приложения, если оно запущено, и выполните следующую команду в консоли диспетчера пакетов (PMC):

Drop-Database
  • Перезапустите приложение.

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

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

  • Откройте обозреватель объектов SQL Server (SSOX) из меню Вид в Visual Studio.
  • В SSOX щелкните (localdb)\MSSQLLocalDB > Базы данных > SchoolContext-{GUID}. Имя базы данных создается на основе имени контекста, которое вы указали ранее, а также включает дефис и GUID.
  • Разверните узел Таблицы.
  • Щелкните правой кнопкой мыши таблицу Student (Учащийся) и выберите пункт Просмотр данных, чтобы просмотреть созданные столбцы и строки, вставленные в таблицу.
  • Щелкните правой кнопкой мыши таблицу Student (Учащийся) и выберите пункт Просмотреть код, чтобы увидеть, как модель Student соотносится со схемой таблицы Student.

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

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

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

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

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

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

Некоторые моменты, которые следует учитывать при написании асинхронного кода, использующего EF Core:

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

Дополнительные сведения об асинхронном программировании см. в разделах Обзор асинхронной модели и Асинхронное программирование с использованием ключевых слов Async и Await.

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