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

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

Это первое руководство из серии, посвященной использованию 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 с ASP.NET Core, а не настройке пользовательского интерфейса.

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

  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. Нажмите кнопку Создать. Диалоговое окно создания проекта ASP.NET Core

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

Скопируйте следующий код и вставьте его в файл 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 текстом об этом приложении.

Запустите приложение, чтобы проверить правильность отображения домашней страницы.

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

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

Схема модели данных "курс-регистрация-учащийся"

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

Сущность 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 для создания указанных ниже компонентов.

  • Класс DbContext EF Core. Контекст —это основной класс, который координирует функциональные возможности Entity Framework для определенной модели данных. Он является производным от класса Microsoft.EntityFrameworkCore.DbContext.
  • Razor Pages с поддержкой операций создания, чтения, обновления и удаления (CRUD) для сущности Student.
  • Создайте папку Pages/Students.
  • В обозревателе решений щелкните правой кнопкой мыши папку Pages/Students и выберите Добавить > Создать шаблонный элемент.
  • В диалоговом окне Добавление нового элемента шаблона выполните указанные ниже действия.
    • На левой вкладке выберите Установленные > Общие > Страницы Razor .
    • Выберите Razor Pages на основе Entity Framework (CRUD)  > Добавить.
  • В диалоговом окне Добавление Razor Pages на основе 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;
  • добавляется контекст для внедрения зависимостей в файле Startup.cs;
  • добавляет строку подключения к базе данных в файл appsettings.json .

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

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

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

{
  "PageSize": 3,
  "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 в ConfigureServices, как показано в следующем коде:

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

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

    services.AddDatabaseDeveloperPageExceptionFilter();
}

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

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

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

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

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

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

Обновите файл 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 -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 class IndexModel : PageModel
{
    private readonly SchoolContext _context;
    private readonly MvcOptions _mvcOptions;

    public IndexModel(SchoolContext context, IOptions<MvcOptions> mvcOptions)
    {
        _context = context;
        _mvcOptions = mvcOptions.Value;
    }

    public IList<Student> Student { get;set; }

    public async Task OnGetAsync()
    {
        Student = await _context.Students.Take(
            _mvcOptions.MaxModelBindingCollectionSize).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();
}

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

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

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

Запустите приложение, чтобы проверить правильность отображения домашней страницы.

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

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

Схема модели данных "курс-регистрация-учащийся"

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

Сущность 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 Pages на основе 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.

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

На примере веб-приложения Contoso University показано, как создать веб-приложение Razor Pages ASP.NET Core с помощью Entity Framework (EF) Core.

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

Скачайте или ознакомьтесь с готовым приложением. Указания по скачиванию.

Предварительные требования

Visual Studio 2019 с указанными ниже рабочими нагрузками.

  • ASP.NET и веб-разработка.
  • Кроссплатформенная разработка .NET Core.

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

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

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

Веб-приложение университета Contoso

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

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

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

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

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

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

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

Изображения для приведенных выше шагов см. в разделе Создание веб-приложения Razor. Запустите приложение.

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

Выполните небольшую настройку меню, макета и домашней страницы сайта. Внесите следующие изменения в файл Pages/Shared/_Layout.cshtml:

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

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

Изменения выделены. (Вся разметка не отображается.)

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

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>
</head>
<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-page="/Index" class="navbar-brand">Contoso University</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-page="/Index">Home</a></li>
                    <li><a asp-page="/About">About</a></li>
                    <li><a asp-page="/Students/Index">Students</a></li>
                    <li><a asp-page="/Courses/Index">Courses</a></li>
                    <li><a asp-page="/Instructors/Index">Instructors</a></li>
                    <li><a asp-page="/Departments/Index">Departments</a></li>
                </ul>
            </div>
        </div>
    </nav>

    <partial name="_CookieConsentPartial" />

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018 : Contoso University</p>
        </footer>
    </div>

    @*Remaining markup not shown for brevity.*@

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

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

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

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

Создайте классы сущностей для приложения университета Contoso. Начните со следующих трех сущностей:

Схема модели данных "курс-регистрация-учащийся"

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

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

Сущность 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. В classnameID``classname — это имя класса. Альтернативным автоматически распознаваемым первичным ключом является StudentID в предыдущем примере.

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

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

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

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

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

Свойство 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

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

  • Выполните построение проекта.
  • Создайте папку Pages/Students.
  • В обозревателе решений щелкните правой кнопкой мыши папку Pages/Students и выберите Добавить > Создать шаблонный элемент.
  • В диалоговом окне Добавление шаблона щелкните Razor Pages на основе Entity Framework (CRUD) > Добавить.

Заполните поля в диалоговом окне Добавление Razor Pages на основе Entity Framework (CRUD) :

  • В раскрывающемся списке Класс модели выберите Student (ContosoUniversity.Models) .
  • В строке Класс контекста данных щелкните значок плюса ( + ) и измените автоматически присвоенное имя на ContosoUniversity.Models.SchoolContext.
  • В раскрывающемся списке Класс контекста данных выберите ContosoUniversity.Models.SchoolContext
  • Нажмите Добавить.

Диалоговое окно CRUD

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

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

Создаваемые файлы

  • Pages/Students Create, Delete, Details, Edit, Index.
  • Data/SchoolContext.cs.

Обновления файла

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

Проверка контекста, зарегистрированного с помощью внедрения зависимостей

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

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

Проверьте метод ConfigureServices в файле Startup.cs. Средством формирования шаблонов была добавлена выделенная строка:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for 
        //non -essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

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

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

Обновление метода Main

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

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

В следующем примере кода показан обновленный файл Program.cs.

using ContosoUniversity.Models;                   // SchoolContext
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;   // CreateScope
using Microsoft.Extensions.Logging;
using System;

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

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

                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }

            host.Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

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

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

  • Удалите базу данных.
  • Измените схему базы данных (например, добавьте поле EmailAddress).
  • Запустите приложение.
  • EnsureCreated создает базу данных со столбцом EmailAddress.

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

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

Запустите приложение и примите политику использования файлов cookie. Это приложение не хранит персональные данные. Сведения о политике использования файлов cookie можно прочитать в разделе Поддержка общего регламента по защите данных ЕС (GDPR).

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

Проверка контекста базы данных SchoolContext

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

Обновите файл SchoolContext.cs, добавив в него следующий код:

using Microsoft.EntityFrameworkCore;

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

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

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

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

DbSet<Enrollment> и DbSet<Course> можно опустить. Платформа EF Core включает эти операторы неявно, так как сущность Student ссылается на сущность Enrollment, а Enrollment ссылается на сущность Course. В рамках данного руководства оставьте DbSet<Enrollment> и DbSet<Course> в SchoolContext.

SQL Server Express LocalDB

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

Добавление кода для инициализации базы данных с использованием тестовых данных

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

В папке Data создайте файл класса с именем DbInitializer.cs и добавьте следующий код:

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

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

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

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

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

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

Примечание. Предыдущий код использует Models для пространства имен (namespace ContosoUniversity.Models) вместо Data. Models согласуется с кодом, созданным средством формирования шаблонов. См. дополнительные сведения о проблеме с формированием шаблонов на GitHub .

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

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

В файле Program.cs измените метод Main, чтобы реализовать вызов Initialize:

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

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

            try
            {
                var context = services.GetRequiredService<SchoolContext>();
                // using ContosoUniversity.Data; 
                DbInitializer.Initialize(context);
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred creating the DB.");
            }
        }

        host.Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

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

Drop-Database

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

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

Разверните узел Таблицы.

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

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

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

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

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

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

public async Task OnGetAsync()
{
    Student = await _context.Student.ToListAsync();
}
  • Ключевое слово async указывает компилятору:

  • Неявный тип возвращаемого значения 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.

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

Дополнительные ресурсы