Руководство. Начало работы с EF Core веб-приложением MVC ASP.NET
Авторы: Том Дайкстра (Tom Dykstra) и Рик Андерсон (Rick Anderson)
Это руководство описывает MVC-модель ASP.NET Core и Entity Framework Core с контроллерами и представлениями. Razor Pages — это альтернативная модель программирования. Для новой разработки мы рекомендуем использовать Razor Pages, а не MVC с контроллерами и представлениями. См. версию этого руководства для Razor Pages. В каждом руководстве содержатся уникальные материалы:
В руководстве по MVC содержатся материалы, которых нет в руководстве по Razor Pages:
- Реализация наследования в модели данных
- Выполнение прямых SQL-запросов
- Использование динамических запросов LINQ для упрощения кода
В руководстве по Razor Pages содержатся материалы, которых нет в этом руководстве:
- Использование метода Select для загрузки связанных данных
- Рекомендации по использованию EF.
На примере веб-приложения для университета Contoso демонстрируется процесс создания веб-приложений ASP.NET Core MVC с помощью Entity Framework (EF) Core и Visual Studio.
В этом примере приложения реализуется веб-сайт вымышленного университета Contoso. На нем предусмотрены различные функции, в том числе прием учащихся, создание курсов и назначение преподавателей. Это первый учебник из серии, в котором описывается создание примера приложения для университета Contoso.
Необходимые компоненты
- Если у вас нет опыта работы с ASP.NET Core MVC, ознакомьтесь с серией учебников по началу работы с ASP.NET Core MVC, прежде чем приступать к изучению этого учебника.
- Visual Studio 2022 с рабочей нагрузкой ASP.NET и веб-разработка.
- Пакет SDK для .NET 6.0
Это руководство не было обновлено для ASP.NET Core 6 или более поздней версии. Инструкции руководства не будут работать правильно, если вы создаете проект, предназначенный для ASP.NET Core 6 или более поздней версии. Например, веб-шаблоны ASP.NET Core 6 и более поздних версий используют минимальную модель размещения, которая объединяет и Program.cs
объединяет Startup.cs
один Program.cs
файл.
Еще одно различие, введенное в .NET 6, — это функция NRT (ссылочные типы, допускающие значение NULL). Шаблоны проектов позволяют включить эту функцию по умолчанию. Проблемы могут возникать, когда EF рассматривает свойство, необходимое в .NET 6, которое допускает значение NULL в .NET 5. Например, страница create Student не будет автоматически завершатся ошибкой, если Enrollments
свойство не имеет значения NULL или asp-validation-summary
вспомогательный тег изменяется с ModelOnly
All
.
Мы рекомендуем установить и использовать пакет SDK для .NET 5 для этого руководства. Пока это руководство не будет обновлено, см. статью Razor Pages with Entity Framework Core в ASP.NET Core . Руководство 1 из 8 по использованию Entity Framework с ASP.NET Core 6 или более поздней версии.
Ядра СУБД
В инструкциях для Visual Studio используется SQL Server LocalDB, версия SQL Server Express, которая работает только в Windows.
Решение проблем и устранение неполадок
Если вы столкнулись с проблемами, для их решения можно попробовать сравнить свой код с кодом готового проекта. Список распространенных ошибок и способы их устранения см. в разделе "Устранение неполадок" последнего руководства серии. Если вы не найдете, что вам нужно, вы можете отправить вопрос, чтобы StackOverflow.com для ASP.NET Core или EF Core.
Совет
Эта серия включает в себя 10 учебников, содержание каждого из которых базируется на предыдущих учебниках. После успешного завершения каждого руководства рекомендуется сохранять копию проекта. Таким образом, при возникновении проблем вы сможете вернуться к предыдущему учебнику, а не к началу серии.
Веб-приложение Contoso University
Приложение, создаваемое в этих руководствах, является простым веб-сайтом университета.
Пользователи приложения могут просматривать и обновлять сведения об учащихся, курсах и преподавателях. Вот несколько экранов в приложении:
Создание веб-приложения
- Откройте Visual Studio и выберите Создать проект.
- В диалоговом окне Создать проект выберите Веб-приложение ASP.NET Core>Далее.
- В диалоговом окне Настроить новый проект введите
ContosoUniversity
в поле Имя проекта. Очень важно использовать именно такое имя с учетом регистра символов, чтобы пространства имен (namespace
) совпадали при копировании кода. - Нажмите кнопку создания.
- В диалоговом окне "Создание нового веб-приложения ASP.NET Core" выберите:
- В раскрывающихся списках выберите .NET Core и ASP.NET Core 5.0.
- Щелкните ASP.NET Core Web App (Model-View-Controller) (Веб-приложение ASP.NET Core (модель — представление — контроллер)).
- Создание
Настройка стиля сайта
Несколько основных изменений настраивают меню сайта, макет и home страницу.
Откройте Views/Shared/_Layout.cshtml
и внесите следующие изменения.
- Измените каждое вхождение
ContosoUniversity
наContoso University
. Таких элементов будет три. - Добавьте пункты меню About (Сведения), Students (Учащиеся), Courses (Курсы), Instructors (Преподаватели) и Departments (Кафедры). Удалите пункт меню Privacy.
Предыдущие изменения выделены в следующем коде:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Contoso University</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="About">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Courses" asp-action="Index">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Departments" asp-action="Index">Departments</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 2020 - Contoso University - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Замените содержимое файла Views/Home/Index.cshtml
следующей разметкой:
@{
ViewData["Title"] = "Home Page";
}
<div class="jumbotron">
<h1>Contoso University</h1>
</div>
<div class="row">
<div class="col-md-4">
<h2>Welcome to Contoso University</h2>
<p>
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core MVC web application.
</p>
</div>
<div class="col-md-4">
<h2>Build it from scratch</h2>
<p>You can build the application by following the steps in a series of tutorials.</p>
<p><a class="btn btn-default" href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the tutorial »</a></p>
</div>
<div class="col-md-4">
<h2>Download it</h2>
<p>You can download the completed project from GitHub.</p>
<p><a class="btn btn-default" href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-mvc/intro/samples/5cu-final">See project source code »</a></p>
</div>
</div>
Нажмите клавиши CTRL+F5, чтобы запустить проект, и выберите в меню Отладка > Запуск без отладки. Страница home отображается с вкладками для страниц, созданных в этом руководстве.
EF Core Пакеты NuGet
В этом учебнике используется SQL Server, для которого требуется пакет поставщика Microsoft.EntityFrameworkCore.SqlServer.
Этот пакет EF SQL Server и его зависимости, Microsoft.EntityFrameworkCore
и Microsoft.EntityFrameworkCore.Relational
, обеспечивают поддержку среды выполнения для платформы EF.
Добавьте пакет NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore. Введите в консоли диспетчера пакетов (PMC) следующие команды, чтобы добавить пакеты NuGet:
Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Пакет Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
NuGet предоставляет ASP.NET ПО промежуточного слоя Core для EF Core страниц ошибок. Это ПО промежуточного слоя помогает обнаруживать и диагностировать ошибки при EF Core миграции.
Сведения о других поставщиках баз данных, доступных для EF Coreпоставщиков баз данных, см. в разделе "Поставщики баз данных".
Создание модели данных
Для этого приложения будут созданы следующие классы сущностей:
У приведенных выше сущностей есть следующие отношения.
- Между сущностями
Student
иEnrollment
действует связь "один ко многим". Учащегося можно зачислить на любое число курсов. - Между сущностями
Course
иEnrollment
действует связь "один ко многим". На курс может быть зачислено любое количество учащихся.
В следующих разделах создается класс для каждой из этих сущностей.
Сущность Student
В папке Models создайте класс Student
, содержащий следующий код:
using System;
using System.Collections.Generic;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Свойство ID
используется в качестве столбца первичного ключа (ПК) в таблице базы данных, соответствующей этому классу. По умолчанию платформа EF интерпретирует в качестве первичного ключа свойство ID
или classnameID
. Например, ПК может называться StudentID
, а не ID
.
Свойство Enrollments
является свойством навигации. Свойства навигации содержат другие сущности, связанные с этой сущностью. Свойство Enrollments
сущности Student
:
- Содержит все сущности
Enrollment
, связанные с этой сущностьюStudent
. - Если в
Student
базе данных есть две связанныеEnrollment
строки:- Свойство навигации
Enrollments
сущностиStudent
содержит эти две сущностиEnrollment
.
- Свойство навигации
Строки Enrollment
содержат значение первичного ключа учащегося в столбце внешнего ключа (ВК) StudentID
.
Если свойство навигации содержит несколько сущностей:
- Тип должен быть списком, например
ICollection<T>
,List<T>
илиHashSet<T>
. - Сущности можно добавлять, удалять и обновлять.
Связи "многие ко многим" и "один ко многим" могут содержать несколько сущностей. Если используется ICollection<T>
, платформа EF по умолчанию создает коллекцию HashSet<T>
.
Сущность Enrollment
В папке Models создайте класс Enrollment
, содержащий следующий код:
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}
Свойство EnrollmentID
— это ПК. Эта сущность использует шаблон classnameID
вместо самого по себе ID
. Сущность Student
использовала шаблон ID
. Некоторые разработчики предпочитают использовать один шаблон во всей модели данных. В этом учебнике демонстрируется возможность использования любого из шаблонов. В одном из следующих учебников показано, за счет чего использование ID
без имени класса позволяет упростить реализацию наследования в модели данных.
Свойство Grade
имеет тип enum
. Знак ?
после объявления типа Grade
указывает, что свойство Grade
допускает значение NULL. Оценка со значением null
отличается от нулевой оценки. При значении null
оценка еще не известна или не назначена.
Свойство StudentID
представляет собой внешний ключ (ВК). Ему соответствует свойство навигации Student
. Сущность Enrollment
связана с одной сущностью Student
, поэтому свойство содержит только отдельную сущность Student
. Она отличается от свойства навигации Student.Enrollments
, которое содержит несколько сущностей Enrollment
.
Свойство CourseID
представляет собой ВК. Ему соответствует свойство навигации Course
. Сущность Enrollment
связана с одной сущностью Course
.
Entity Framework интерпретирует свойство как свойство ВК, если оно называется <
имя_свойства_навигации><
имя_свойства_первичного_ключа>
. Например, StudentID
для свойства навигации Student
, так как сущность Student
имеет значение ПК ID
. Свойства ВК также могут называться <
имя_свойства_первичного_ключа>
. Например, CourseID
, так как сущность Course
имеет значение ПК CourseID
.
Сущность Course
В папке Models создайте класс Course
, содержащий следующий код:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Свойство Enrollments
является свойством навигации. Сущность Course
может быть связана с любым числом сущностей Enrollment
.
Атрибут DatabaseGenerated описан в следующем учебнике. Этот атрибут позволяет ввести ПК для курса, а не использовать базу данных, чтобы создать его.
Создание контекста базы данных
Контекст базы данных DbContext — это основной класс, который координирует функциональные возможности EF для определенной модели данных. Этот класс является производным от класса Microsoft.EntityFrameworkCore.DbContext
. Производный класс DbContext
указывает сущности, которые включаются в модель данных. Некоторые расширения функциональности EF можно настроить. В этом проекте соответствующий класс называется SchoolContext
.
В папке проекта создайте папку с именем Data
.
В папке Data создайте файл SchoolContext
, содержащий следующий код:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
}
}
Представленный выше код создает свойство DbSet
для каждого набора сущностей. Терминология EF:
- Набор сущностей обычно соответствует таблице базы данных.
- Сущность соответствует строке в таблице.
Инструкции DbSet<Enrollment>
и DbSet<Course>
можно опустить. Это не нарушит функциональность. EF включает их неявно, так как
- сущность
Student
ссылается на сущностьEnrollment
, - сущность
Enrollment
ссылается на сущностьCourse
,
При создании базы данных платформа EF создает таблицы с именами, соответствующими именам свойств в DbSet
. Имена свойств для коллекций обычно указаны во множественном числе. Например, используйте идентификатор Students
вместо Student
. В среде разработчиков нет единого мнения о том, следует ли использовать имена таблиц во множественном числе. В этих учебниках вместо принятого по умолчанию способа таблицам в DbContext
присваиваются имена в единственном числе. Чтобы сделать это, добавьте выделенный ниже код после последнего свойства DbSet.
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}
Зарегистрируйте SchoolContext
.
ASP.NET Core поддерживает внедрение зависимостей. С помощью внедрения зависимостей службы, например контекст базы данных EF, регистрируются во время запуска приложения. Затем компоненты, которые используют эти службы, например контроллеры MVC, обращаются к ним через параметры конструктора. Код конструктора контроллера, который получает экземпляр контекста, будет приведен позднее в этом учебнике.
Чтобы зарегистрировать SchoolContext
как службу, откройте файл Startup.cs
и добавьте выделенные строки в метод ConfigureServices
.
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace ContosoUniversity
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllersWithViews();
}
Имя строки подключения передается в контекст путем вызова метода для объекта DbContextOptionsBuilder
. При локальной разработке система конфигурации ASP.NET Core считывает строку подключения из файла appsettings.json
.
Откройте файл appsettings.json
и добавьте строку подключения, как показано в следующей разметке:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Добавление фильтра исключений базы данных
Добавьте AddDatabaseDeveloperPageExceptionFilter в ConfigureServices
, как показано в следующем коде:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddControllersWithViews();
}
AddDatabaseDeveloperPageExceptionFilter
предоставляет полезные сведения об ошибках в среде разработки.
SQL Server Express LocalDB
Строка подключения указывает базу данных SQL Server LocalDB. LocalDB — это упрощенная версия ядра СУБД SQL Server Express, предназначенная для разработки приложений и не ориентированная на использование в рабочей среде. LocalDB запускается по запросу в пользовательском режиме, поэтому настройки не слишком сложны. По умолчанию LocalDB создает файлы базы данных MDF в каталоге C:/Users/<user>
.
Инициализация базы данных с тестовыми данными
EF создает пустую базу данных. В этом разделе добавляется метод, который вызывается после создания базы данных и заполняет ее тестовыми данными.
Метод EnsureCreated
будет использоваться для автоматического создания базы данных. В одном из следующих учебников вы узнаете, как обрабатывать изменения модели с использованием Code First Migrations, что позволяет изменять схему базы данных вместо того, чтобы удалять и повторно создавать ее.
В папке Data создайте файл с именем DbInitializer
, содержащий следующий код:
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var students = new Student[]
{
new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
};
foreach (Student s in students)
{
context.Students.Add(s);
}
context.SaveChanges();
var courses = new Course[]
{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
foreach (Enrollment e in enrollments)
{
context.Enrollments.Add(e);
}
context.SaveChanges();
}
}
}
Предыдущий код проверяет, существует ли база данных:
- Если база данных не найдена;
- она создается и загружается вместе с тестовыми данными. Для повышения производительности тестовые данные загружаются массивами, а не коллекциями
List<T>
.
- она создается и загружается вместе с тестовыми данными. Для повышения производительности тестовые данные загружаются массивами, а не коллекциями
- Если база данных найдена, никакие действия не выполняются.
Обновите Program.cs
, включив в него следующий код.
using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
CreateDbIfNotExists(host);
host.Run();
}
private static void CreateDbIfNotExists(IHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
Program.cs
выполняет следующие действия при запуске приложения:
- Получение экземпляра контекста базы данных из контейнера внедрения зависимостей.
- Вызовите метод
DbInitializer.Initialize
. - Удалите контекст по завершении работы метода
Initialize
, как показано в следующем коде:
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}
host.Run();
}
При первом запуске приложения создается база данных, которая заполняется тестовыми данными. Каждый раз при изменении модели данных:
- Удалите базу данных.
- Обновите метод заполнения и запустите заново с новой базой данных.
В последующих учебниках описывается, как изменить базу данных при изменении модели данных, не прибегая к ее удалению и повторному созданию. При изменении модели данных данные не теряются.
Создание контроллера и представлений
Используйте подсистему формирования шаблонов Visual Studio для добавления контроллера и представлений MVC, которые будут использовать платформу EF для запроса данных и их сохранения.
Автоматическое создание методов и представлений операций CRUD (создание, чтение, обновление и удаление) называется формированием шаблонов.
- В обозревателе решений щелкните правой кнопкой мыши папку
Controllers
и выберите Добавить > Создать шаблонный элемент. - В диалоговом окне "Добавление шаблонов":
- Выберите Контроллер MVC с представлениями, использующий Entity Framework.
- Нажмите кнопку Добавить. Появится диалоговое окно "Добавить контроллер MVC с представлениями с помощью Entity Framework" (Добавление контроллера MVC с представлениями с помощью Entity Framework ):
- В разделе Класс модели выберите Student.
- В разделе Класс контекста данных выберите SchoolContext.
- Оставьте предлагаемое по умолчанию имя StudentsController.
- Нажмите кнопку Добавить.
Подсистема формирования шаблонов Visual Studio создает файл StudentsController.cs
и набор представлений (файлы *.cshtml
), которые будут работать с контроллером.
Обратите внимание, что контроллер принимает SchoolContext
в качестве параметра конструктора.
namespace ContosoUniversity.Controllers
{
public class StudentsController : Controller
{
private readonly SchoolContext _context;
public StudentsController(SchoolContext context)
{
_context = context;
}
Технология внедрения зависимостей ASP.NET Core обеспечивает передачу экземпляра SchoolContext
в контроллер. Вы настраиваете ее в классе Startup
.
Контроллер содержит метод действия Index
, который отображает всех учащихся в базе данных. Этот метод получает список учащихся из набора сущностей Students, считывая свойство Students
экземпляра контекста базы данных:
public async Task<IActionResult> Index()
{
return View(await _context.Students.ToListAsync());
}
Элементы асинхронного программирования в этом коде рассматриваются далее в этом учебнике.
В Views/Students/Index.cshtml
представлении отображается этот список в таблице:
@model IEnumerable<ContosoUniversity.Models.Student>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
@Html.DisplayNameFor(model => model.EnrollmentDate)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Нажмите клавиши CTRL+F5, чтобы запустить проект, и выберите в меню Отладка > Запуск без отладки.
Перейдите на вкладку Students (Учащиеся), чтобы просмотреть тестовые данные, добавленные методом DbInitializer.Initialize
. В зависимости от размеров окна браузера ссылку на вкладку Students
можно найти вверху страницы или щелкнув значок навигации в правом верхнем углу.
Просмотр базы данных
При запуске приложения метод DbInitializer.Initialize
вызывает EnsureCreated
. Платформа EF определила, что база данных отсутствует,
- поэтому создала базу данных.
- Код метода
Initialize
заполняет базу данных данными.
Для просмотра базы данных в Visual Studio используйте Обозреватель объектов SQL Server (SSOX).
- Выберите Обозреватель объектов SQL Server из меню Вид в Visual Studio.
- В SSOX щелкните (localdb)\MSSQLLocalDB > Базы данных.
- Выберите запись
ContosoUniversity1
для имени базы данных, которая находится в строке подключения в файлеappsettings.json
. - Разверните узел Таблицы, чтобы просмотреть представленные в базе таблицы.
Щелкните правой кнопкой мыши таблицу Student и выберите пункт Просмотреть данные, чтобы просмотреть данные в таблице.
Файлы базы данных *.mdf
и *.ldf
находятся в папке C:\Users\<имя_пользователя>.
Так как EnsureCreated
вызывается в методе инициализатора, который выполняется при запуске приложения, можно сделать следующее:
- Внести изменение в класс
Student
. - Удалите базу данных.
- Остановить, а затем запустить приложение. База данных автоматически создается повторно в соответствии с изменением.
Например, при добавлении свойства EmailAddress
в класс Student
во вновь созданной таблице появится новый столбец EmailAddress
. В представлении не будет отображаться новое свойство EmailAddress
.
Соглашения
Объем кода, написанного для создания полной базы данных платформой EF, сведен к минимуму благодаря использованию соглашений EF.
- В качестве имен таблиц используются имена свойств
DbSet
. Для сущностей, на которые не ссылается свойствоDbSet
, в качестве имен таблиц используются имена классов сущностей. - В качестве имен столбцов используются имена свойств сущностей.
- Свойства сущности с именами
ID
илиclassnameID
распознаются как свойства ПК. - Свойство интерпретируется как свойство ВК, если оно называется
<
имя_свойства_навигации><
имя_свойства_первичного_ключа>
. Например,StudentID
для свойства навигацииStudent
, так как сущностьStudent
имеет значение ПКID
. Свойства ВК также могут называться<
имя_свойства_первичного_ключа>
. Например,EnrollmentID
, так как сущностьEnrollment
имеет ПКEnrollmentID
.
Стандартное поведение можно переопределить. Например, можно явно задать имена таблиц, как было показано ранее в этом учебнике. Имена столбцов и любое свойство можно задать в качестве ПК или ВК.
Асинхронный код
Асинхронное программирование — это режим по умолчанию для ASP.NET Core и EF Core.
Веб-сервер имеет ограниченное число потоков, поэтому при высокой загрузке могут использоваться все доступные потоки. В таких случаях сервер не может обрабатывать новые запросы до тех пор, пока не будут высвобождены потоки. В синхронном коде многие потоки могут быть заняты, не выполняя при этом какие-либо операции и ожидая завершения ввода-вывода. В асинхронном коде в то время, когда процесс ожидает завершения ввода-вывода, его поток высвобождается и может использоваться сервером для обработки других запросов. Таким образом, асинхронный код позволяет более эффективно использовать ресурсы сервера, который может обрабатывать больше трафика без задержек.
Во время выполнения асинхронный код использует немного больше служебных ресурсов, однако при низком объеме трафика этим можно пренебречь. Тем не менее в случае большого объема трафика это дает существенный выигрыш в производительности.
В следующем коде async
, Task<T>
, await
и ToListAsync
обеспечивают асинхронное выполнение кода.
public async Task<IActionResult> Index()
{
return View(await _context.Students.ToListAsync());
}
- Ключевое слово
async
указывает компилятору создавать обратные вызовы для частей тела метода и автоматически создавать возвращаемый объектTask<IActionResult>
. - Тип возвращаемого значения
Task<IActionResult>
представляет текущую операцию с помощью результата типаIActionResult
. - Ключевое слово
await
предписывает компилятору разделить метод на две части. Первая часть завершается операцией, которая запускается в асинхронном режиме. Вторая часть помещается в метод обратного вызова, который вызывается при завершении операции. ToListAsync
является асинхронной версией метода расширенияToList
.
При написании асинхронного кода, который использует EF, нужно учитывать некоторые моменты:
- Асинхронно выполняются только те инструкции, в результате которых в базу данных отправляются запросы или команды. К ним относятся, например,
ToListAsync
,SingleOrDefaultAsync
иSaveChangesAsync
. В их число не входят, например, инструкции, которые просто изменяютIQueryable
, такие какvar students = context.Students.Where(s => s.LastName == "Davolio")
. - Контекст EF не является потокобезопасным, поэтому не следует пытаться выполнять несколько операций параллельно. При вызове любого асинхронного метода EF всегда используйте ключевое слово
await
. - Чтобы воспользоваться преимуществами в производительности, которые обеспечивает асинхронный код, убедитесь, что все используемые пакеты библиотек также используют асинхронный код при вызове любых методов EF, выполняющих запросы к базе данных.
Дополнительные сведения об асинхронных методах программирования в .NET см. в разделе Обзор асинхронной модели.
Ограничение полученных сущностей
Сведения об ограничении числа сущностей, возвращаемых в запросе, см. в статье Важные замечания о производительности.
Ведение журнала SQL Entity Framework Core
Конфигурация ведения журналов обычно предоставляется разделом Logging
в файлах appsettings.{Environment}.json
. Чтобы регистрировать инструкции SQL, добавьте "Microsoft.EntityFrameworkCore.Database.Command": "Information"
в файл appsettings.Development.json
:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDB-2;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
,"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
},
"AllowedHosts": "*"
}
В приведенном выше коде JSON инструкции SQL отображаются в командной строке и в окне вывода Visual Studio.
Дополнительные сведения см. в статье Ведение журнала в ASP.NET Core и описании этой проблемы GitHub.
В следующем учебнике описано, как выполнять основные операции CRUD (создание, чтение, обновление и удаление).
Это руководство описывает MVC-модель ASP.NET Core и Entity Framework Core с контроллерами и представлениями. Razor Pages — это альтернативная модель программирования. Для новой разработки мы рекомендуем использовать Razor Pages, а не MVC с контроллерами и представлениями. См. версию этого руководства для Razor Pages. В каждом руководстве содержатся уникальные материалы:
В руководстве по MVC содержатся материалы, которых нет в руководстве по Razor Pages:
- Реализация наследования в модели данных
- Выполнение прямых SQL-запросов
- Использование динамических запросов LINQ для упрощения кода
В руководстве по Razor Pages содержатся материалы, которых нет в этом руководстве:
- Использование метода Select для загрузки связанных данных
- Рекомендации по использованию EF.
Пример веб-приложения Contoso University демонстрирует создание веб-приложений ASP.NET Core 2.2 MVC с помощью Entity Framework (EF) Core 2.2 и Visual Studio 2019.
Это руководство не было обновлено для использования с версией ASP.NET Core 3.1. Оно обновлено для использования с ASP.NET Core 5.0.
В этом примере приложения реализуется веб-сайт вымышленного университета Contoso. На нем предусмотрены различные функции, в том числе прием учащихся, создание курсов и назначение преподавателей. Это первый учебник из серии, в котором с самого начала описывается построение примера приложения для университета Contoso.
Необходимые компоненты
- Пакет SDK для .NET Core 2.2
- Visual Studio 2019 со следующими рабочими нагрузками:
- Рабочая нагрузка ASP.NET и веб-разработка
- Рабочая нагрузка Кроссплатформенная разработка .NET Core
Устранение неполадок
Если вы столкнулись с проблемами, для их решения можно попробовать сравнить свой код с кодом готового проекта. Список распространенных ошибок и способы их устранения см. в разделе "Устранение неполадок" последнего руководства серии. Если вы не найдете, что вам нужно, вы можете отправить вопрос, чтобы StackOverflow.com для ASP.NET Core или EF Core.
Совет
Эта серия включает в себя 10 учебников, содержание каждого из которых базируется на предыдущих учебниках. После успешного завершения каждого руководства рекомендуется сохранять копию проекта. Таким образом, при возникновении проблем вы сможете вернуться к предыдущему учебнику, а не к началу серии.
Веб-приложение Contoso University
В рамках этих учебников вы будете создавать приложение, которое представляет собой простой веб-сайт университета.
Пользователи приложения могут просматривать и обновлять сведения об учащихся, курсах и преподавателях. Будет создано несколько экранов.
Создание веб-приложения
Откройте Visual Studio.
В меню Файл выберите пункт Создать > Проект.
В области слева выберите Установленные > Visual C# > Интернет.
Выберите шаблон проекта Веб-приложение ASP.NET Core.
Введите имя ContosoUniversity и нажмите кнопку ОК.
Дождитесь появления диалогового окна Создание веб-приложения ASP.NET Core.
Выберите .NET Core, ASP.NET Core 2.2 и шаблон Веб-приложение (модель — представление — контроллер).
Убедитесь, что для параметра Проверка подлинности задано значение Без проверки подлинности.
Выберите ОК
Настройка стиля сайта
Несколько простых изменений настраивают меню сайта, макет и home страницу.
Откройте Views/Shared/_Layout.cshtml
и внесите следующие изменения.
Замените все вхождения "ContosoUniversity" на "Contoso University". Таких элементов будет три.
Добавьте пункты меню About (Сведения), Students (Учащиеся), Courses (Курсы), Instructors (Преподаватели) и Departments (Кафедры). Удалите пункт меню Privacy.
Изменения выделены.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
crossorigin="anonymous"
integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/>
</environment>
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Contoso University</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="About">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Courses" asp-action="Index">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Departments" asp-action="Index">Departments</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<partial name="_CookieConsentPartial" />
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 2019 - Contoso University - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
</environment>
<environment exclude="Development">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"
asp-fallback-src="~/lib/jquery/dist/jquery.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">
</script>
</environment>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>
Замените содержимое файла Views/Home/Index.cshtml
следующим кодом, который заменяет текст об ASP.NET и MVC на текст о текущем приложении:
@{
ViewData["Title"] = "Home Page";
}
<div class="jumbotron">
<h1>Contoso University</h1>
</div>
<div class="row">
<div class="col-md-4">
<h2>Welcome to Contoso University</h2>
<p>
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core MVC web application.
</p>
</div>
<div class="col-md-4">
<h2>Build it from scratch</h2>
<p>You can build the application by following the steps in a series of tutorials.</p>
<p><a class="btn btn-default" href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the tutorial »</a></p>
</div>
<div class="col-md-4">
<h2>Download it</h2>
<p>You can download the completed project from GitHub.</p>
<p><a class="btn btn-default" href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-mvc/intro/samples/cu-final">See project source code »</a></p>
</div>
</div>
Нажмите клавиши CTRL+F5, чтобы запустить проект, и выберите в меню Отладка > Запуск без отладки. Вы увидите home страницу со вкладками для страниц, которые вы создадите в этих руководствах.
Сведения о EF Core пакетах NuGet
Чтобы добавить EF Core поддержку в проект, установите поставщик базы данных, который вы хотите нацелить. В этом учебнике используется SQL Server, для которого требуется пакет поставщика Microsoft.EntityFrameworkCore.SqlServer. Этот пакет входит в метапакет Microsoft.AspNetCore.App, поэтому ссылаться на него не нужно.
Этот пакет EF SQL Server и его зависимости (Microsoft.EntityFrameworkCore
и Microsoft.EntityFrameworkCore.Relational
) обеспечивают поддержку среды выполнения для платформы EF. Пакет средств будет добавлен позднее в рамках учебника Миграции.
Дополнительные сведения о других поставщиках баз данных, которые доступны для платформы Entity Framework Core, см. в разделе Поставщики баз данных.
Создание модели данных
Теперь необходимо создать классы сущностей для приложения университета Contoso. Для начала создаются следующие три сущности.
Между сущностями Student
и Enrollment
, а также между сущностями Course
и Enrollment
существует отношение "один ко многим". Другими словами, учащийся может быть зарегистрирован в любом количестве курсов, а в отдельном курсе может быть зарегистрировано любое количество учащихся.
В следующих разделах создаются классы для каждой из этих сущностей.
Сущность Student
В папке Models создайте файл класса с именем Student.cs
и замените код шаблона следующим кодом.
using System;
using System.Collections.Generic;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Свойство ID
будет использоваться в качестве столбца первичного ключа в таблице базы данных, соответствующей этому классу. По умолчанию платформа Entity Framework интерпретирует в качестве первичного ключа свойство ID
или classnameID
.
Свойство Enrollments
является свойством навигации. Свойства навигации содержат другие сущности, связанные с этой сущностью. В этом случае свойство Enrollments
сущности Student entity
содержит все сущности Enrollment
, которые связаны с этой сущностью Student
. Другими словами, если со строкой Student
в базе данных связаны две строки Enrollment
(строки, в столбце внешнего ключа StudentID которых содержится первичный ключ этого учащегося), в этой сущности Student
свойство навигации Enrollments
будет содержать две этих сущности Enrollment
.
Если свойство навигации может содержать несколько сущностей (как в отношениях "многие ко многим" или "один ко многим"), оно должно иметь тип списка, допускающий добавление, удаление и обновление записей, такой как ICollection<T>
. Вы можете указать тип ICollection<T>
либо, например, тип List<T>
или HashSet<T>
. Если указан тип ICollection<T>
, платформа EF по умолчанию создает коллекцию HashSet<T>
.
Сущность Enrollment
В папке Models создайте Enrollment.cs
и замените существующий код следующим кодом:
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}
Свойство EnrollmentID
будет использоваться в качестве первичного ключа. В этой сущности используется шаблон classnameID
вместо ID
, как в сущности Student
. Как правило, следует выбирать один шаблон, который будет использоваться в рамках всей модели данных. В этом случае демонстрируется возможность использования любого из шаблонов. В одном из следующих учебников вы узнаете, за счет чего использование идентификатора без имени класса позволяет упростить реализацию наследования в модели данных.
Свойство Grade
имеет тип enum
. Знак вопроса после объявления типа Grade
указывает, что свойство Grade
допускает значение null. Оценка со значением null отличается от нулевой оценки тем, что при таком значении оценка еще не известна или не назначена.
Свойство StudentID
представляет собой внешний ключ. Ему соответствует свойство навигации Student
. Сущность Enrollment
связана с одной сущностью Student
, поэтому это свойство может содержать одну сущность Student
(в отличие от представленного ранее свойства навигации Student.Enrollments
, которое может содержать несколько сущностей Enrollment
).
Свойство CourseID
представляет собой внешний ключ. Ему соответствует свойство навигации Course
. Сущность Enrollment
связана с одной сущностью Course
.
Платформа Entity Framework интерпретирует свойство как свойство внешнего ключа, если оно имеет имя <navigation property name><primary key property name>
(например, StudentID
для свойства навигации Student
, поскольку сущность Student
имеет первичный ключ ID
). Свойства внешнего ключа также могут называться просто <primary key property name>
(например CourseID
, поскольку сущность Course
имеет первичный ключ CourseID
).
Сущность Course
В папке Models создайте Course.cs
и замените существующий код следующим кодом:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Свойство Enrollments
является свойством навигации. Сущность Course
может быть связана с любым числом сущностей Enrollment
.
Более подробное описание атрибута DatabaseGenerated
будет приведено в одном из следующих учебников этой серии. Фактически, этот атрибут позволяет ввести первичный ключ для курса, а не использовать базу данных, чтобы создать его.
Создание контекста базы данных
Контекст базы данных — это основной класс, который координирует функциональные возможности Entity Framework для заданной модели данных. Этот класс создается путем наследования от класса Microsoft.EntityFrameworkCore.DbContext
. В коде указываются сущности, которые включаются в модель данных. Также вы можете настроить реакцию платформы Entity Framework на некоторые события. В этом проекте соответствующий класс называется SchoolContext
.
В папке проекта создайте папку Data.
В папке данных создайте файл класса с именем SchoolContext.cs
и замените код шаблона следующим кодом:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
}
}
Этот код создает свойство DbSet
для каждого набора сущностей. В терминологии Entity Framework набор сущностей обычно соответствует таблице базы данных, а сущность — строке в этой таблице.
Вы можете опустить инструкции DbSet<Enrollment>
и DbSet<Course>
. Это не нарушит функциональность. Платформа Entity Framework включает эти инструкции неявно, поскольку сущность Student
ссылается на сущность Enrollment
, а Enrollment
ссылается на сущность Course
.
При создании базы данных платформа EF создает таблицы с именами, соответствующими именам свойств в DbSet
. Имена свойств для коллекций, как правило, задаются во множественном числе (например, Students вместо Student), однако единого мнения по поводу присвоения имен во множественном числе таблицам среди разработчиков не существует. В этих учебниках вместо принятого по умолчанию способа таблицам в DbContext присваиваются имена в единственном числе. Чтобы сделать это, добавьте выделенный ниже код после последнего свойства DbSet.
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}
Выполните сборку проекта, чтобы проверить его на ошибки компиляции.
Регистрация SchoolContext
ASP.NET Core по умолчанию реализует технологию внедрения зависимостей. С помощью внедрения зависимостей службы (например, контекст базы данных EF) регистрируются во время запуска приложения. Затем компоненты, которые используют эти службы (например, контроллеры MVC), обращаются к ним через параметры конструктора. Код конструктора контроллера, который получает экземпляр контекста, будет приведен позднее в этом учебнике.
Чтобы зарегистрировать SchoolContext
как службу, откройте файл Startup.cs
и добавьте выделенные строки в метод ConfigureServices
.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
}
Имя строки подключения передается в контекст путем вызова метода для объекта DbContextOptionsBuilder
. При локальной разработке система конфигурации ASP.NET Core считывает строку подключения из файла appsettings.json
.
Добавьте инструкции using
для пространств имен ContosoUniversity.Data
и Microsoft.EntityFrameworkCore
, после чего выполните построение проекта.
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Http;
Откройте файл appsettings.json
и добавьте строку подключения, как показано в следующем примере.
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
SQL Server Express LocalDB
Строка подключения указывает на базу данных SQL Server LocalDB. LocalDB — это упрощенная версия ядра СУБД SQL Server Express, предназначенная для разработки приложений и не ориентированная на использование в производственной среде. LocalDB запускается по запросу в пользовательском режиме, поэтому настройки не слишком сложны. По умолчанию база данных LocalDB создает файлы .mdf в каталоге C:/Users/<user>
.
Инициализация базы данных с тестовыми данными
Платформа Entity Framework создает пустую базу данных. В этом разделе вы напишете метод, который вызывается после создания базы данных и заполняет ее тестовыми данными.
Здесь будет использоваться метод EnsureCreated
для автоматического создания базы данных. В одном из следующих учебников вы узнаете, как обрабатывать изменения модели с использованием Code First Migrations, что позволяет изменять схему базы данных вместо того, чтобы удалять и повторно создавать ее.
В папке данных создайте новый файл класса с именем DbInitializer.cs
и замените код шаблона следующим кодом, что приводит к созданию базы данных при необходимости и загрузке тестовых данных в новую базу данных.
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var students = new Student[]
{
new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
};
foreach (Student s in students)
{
context.Students.Add(s);
}
context.SaveChanges();
var courses = new Course[]
{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
foreach (Enrollment e in enrollments)
{
context.Enrollments.Add(e);
}
context.SaveChanges();
}
}
}
Этот код проверяет, добавлены ли в базу данных учащиеся. Если нет, база данных считается новой и заполняется тестовыми данными. Для повышения производительности тестовые данные загружаются массивами, а не коллекциями List<T>
.
В файле Program.cs
измените метод Main
, чтобы реализовать следующее поведение при запуске приложения:
- Получение экземпляра контекста базы данных из контейнера внедрения зависимостей.
- Вызов метода инициализации с передачей ему контекста.
- Высвобождение контекста после завершения работы метода заполнения.
using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
CreateDbIfNotExists(host);
host.Run();
}
private static void CreateDbIfNotExists(IHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
При первом запуске приложения будет создана и заполнена тестовыми данными необходимая для работы база данных. При каждом изменении модели данных:
- Удалите базу данных.
- Обновите метод заполнения и запустите заново с новой базой данных таким же образом.
В следующих учебниках вы узнаете, как изменить базу данных при изменении модели данных, не прибегая к ее удалению и повторному созданию.
Создание контроллера и представлений
В этом разделе подсистема формирования шаблонов Visual Studio используется для добавления контроллера и представлений MVC, которые будут использовать платформу EF для запроса данных и их сохранения.
Автоматическое создание методов и представлений операций CRUD (создание, чтение, обновление и удаление) называется формированием шаблонов. Формирование шаблонов отличается от создания кода тем, что шаблонный код является отправной точкой и может изменяться в соответствии с потребностями, тогда как сформированный код обычно не изменяется. В тех случаях, когда требуется настроить созданный код в соответствии с внесенными изменениями, вы можете использовать разделяемые классы или повторно создать код.
- Щелкните правой кнопкой мыши папку Контроллеры в обозревателе решений и выберите Добавить > Создать шаблонный элемент.
- В диалоговом окне "Добавление шаблонов":
- Выберите Контроллер MVC с представлениями, использующий Entity Framework.
- Нажмите кнопку Добавить. Появится диалоговое окно "Добавить контроллер MVC с представлениями с помощью Entity Framework" (Добавление контроллера MVC с представлениями с помощью Entity Framework ):
- В разделе Класс модели выберите Student.
- В разделе Класс контекста данных выберите SchoolContext.
- Оставьте предлагаемое по умолчанию имя StudentsController.
- Нажмите кнопку Добавить.
Подсистема формирования шаблонов Visual Studio создает файл StudentsController.cs
и набор представлений (файлы .cshtml
), которые будут работать с контроллером.
Обратите внимание, что контроллер принимает SchoolContext
в качестве параметра конструктора.
namespace ContosoUniversity.Controllers
{
public class StudentsController : Controller
{
private readonly SchoolContext _context;
public StudentsController(SchoolContext context)
{
_context = context;
}
Технология внедрения зависимостей ASP.NET Core обеспечивает передачу экземпляра SchoolContext
в контроллер. Это было настроено в файле Startup.cs
.
Контроллер содержит метод действия Index
, который отображает всех учащихся в базе данных. Этот метод получает список учащихся из набора сущностей Students, считывая свойство Students
экземпляра контекста базы данных:
public async Task<IActionResult> Index()
{
return View(await _context.Students.ToListAsync());
}
Позднее в этом учебнике будут описаны элементы асинхронного программирования в этом коде.
В Views/Students/Index.cshtml
представлении отображается этот список в таблице:
@model IEnumerable<ContosoUniversity.Models.Student>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
@Html.DisplayNameFor(model => model.EnrollmentDate)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Нажмите клавиши CTRL+F5, чтобы запустить проект, и выберите в меню Отладка > Запуск без отладки.
Перейдите на вкладку Students (Учащиеся), чтобы просмотреть тестовые данные, добавленные методом DbInitializer.Initialize
. В зависимости от размеров окна браузера ссылку на вкладку Students
можно найти вверху страницы или щелкнув значок навигации в правом верхнем углу.
Просмотр базы данных
При запуске приложения метод DbInitializer.Initialize
вызывает метод EnsureCreated
. Платформа EF определяет, что база данных отсутствует, и создает ее, после чего код в оставшейся части метода Initialize
заполняет базу данными. Для просмотра базы данных в Visual Studio можно использовать обозреватель объектов SQL Server (SSOX).
Закройте браузер.
Если окно SSOX не открылось, выберите его в меню Вид Visual Studio.
В окне SSOX щелкните (localdb)\MSSQLLocalDB > Базы данных, а затем щелкните запись базы данных, имя которой указано в строке подключения в вашем файле appsettings.json
.
Разверните узел Таблицы, чтобы просмотреть представленные в базе таблицы.
Щелкните правой кнопкой мыши таблицу Student и выберите пункт Просмотр данных, чтобы просмотреть созданные столбцы и строки, вставленные в базу данных.
Файлы базы данных с расширениями MDF и LDF находятся в папке C:\Users\<имя_пользователя>.
Поскольку вы вызываете метод EnsureCreated
в методе инициализатора, который выполняется при запуске приложения, теперь вы можете внести изменения в класс Student
, удалить базу данных и снова запустить приложение. После этого база данных будет создана повторно в соответствии с внесенными изменениями. Например, при добавлении свойства EmailAddress
в класс Student
во вновь созданной таблице появится столбец EmailAddress
.
Соглашения
Чтобы платформа Entity Framework автоматически создавала полную базу данных на основе принятых соглашений и допущений, потребуется написать минимальный объем кода.
- В качестве имен таблиц используются имена свойств
DbSet
. Для сущностей, на которые не ссылается свойствоDbSet
, в качестве имен таблиц используются имена классов сущностей. - В качестве имен столбцов используются имена свойств сущностей.
- Свойства сущностей с именем ID или classnameID распознаются как свойства первичного ключа.
- Свойство интерпретируется как свойство внешнего ключа, если оно имеет имя <имя_свойства_навигации><имя_свойства_первичного_ключа> (например
StudentID
для свойства навигацииStudent
, так как сущностьStudent
имеет первичный ключID
). Свойства внешнего ключа также могут называться просто <имя_свойства_первичного_ключа> (напримерEnrollmentID
, так как сущностьEnrollment
имеет первичный ключEnrollmentID
).
Стандартное поведение можно переопределить. Например, можно явно задать имена таблиц, как было показано ранее в этом учебнике. Также можно указать имена столбцов и задать любое свойство в качестве первичного или внешнего ключа, как будет показано в одном из следующих учебников этой серии.
Асинхронный код
Асинхронное программирование — это режим по умолчанию для ASP.NET Core и EF Core.
Веб-сервер имеет ограниченное число потоков, поэтому при высокой загрузке могут использоваться все доступные потоки. В таких случаях сервер не может обрабатывать новые запросы до тех пор, пока не будут высвобождены потоки. В синхронном коде многие потоки могут быть заняты, не выполняя при этом какие-либо операции и ожидая завершения ввода-вывода. В асинхронном коде в то время, когда процесс ожидает завершения ввода-вывода, его поток высвобождается и может использоваться сервером для обработки других запросов. Таким образом, асинхронный код позволяет более эффективно использовать ресурсы сервера, который может обрабатывать больше трафика без задержек.
Во время выполнения асинхронный код использует немного больше служебных ресурсов, однако при низком объеме трафика этим можно пренебречь. Тем не менее в случае большого объема трафика это дает существенный выигрыш в производительности.
В следующем коде для асинхронного выполнения используются ключевое слово async
, возвращаемое значение Task<T>
, ключевое слово await
и метод ToListAsync
.
public async Task<IActionResult> Index()
{
return View(await _context.Students.ToListAsync());
}
- Ключевое слово
async
указывает компилятору создавать обратные вызовы для частей тела метода и автоматически создавать возвращаемый объектTask<IActionResult>
. - Тип возвращаемого значения
Task<IActionResult>
представляет текущую операцию с помощью результата типаIActionResult
. - Ключевое слово
await
предписывает компилятору разделить метод на две части. Первая часть завершается операцией, которая запускается в асинхронном режиме. Вторая часть помещается в метод обратного вызова, который вызывается при завершении операции. ToListAsync
является асинхронной версией метода расширенияToList
.
При написании асинхронного кода, который использует Entity Framework, необходимо учитывать некоторые моменты:
- Асинхронно выполняются только те инструкции, в результате которых в базу данных отправляются запросы или команды. К ним относятся, например,
ToListAsync
,SingleOrDefaultAsync
иSaveChangesAsync
. В их число не входят, например, инструкции, которые просто изменяютIQueryable
, такие какvar students = context.Students.Where(s => s.LastName == "Davolio")
. - Контекст EF не является потокобезопасным, поэтому не следует пытаться выполнять несколько операций параллельно. При вызове любого асинхронного метода EF всегда используйте ключевое слово
await
. - Если вы хотите использовать преимущества в производительности, которые обеспечивает асинхронный код, убедитесь, что все используемые пакеты библиотек (например, для разбиения на страницы) также используют асинхронный код при вызове любых методов Entity Framework, выполняющих запросы к базе данных.
Дополнительные сведения об асинхронных методах программирования в .NET см. в разделе Обзор асинхронной модели.
Следующие шаги
В следующем учебнике описано, как выполнять основные операции CRUD (создание, чтение, обновление и удаление).
ASP.NET Core