Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core
Кроме моделей со связью один-ко-одному и один-ко-многим, которые были рассмотрены в прошлых темах, модели по типу "многие-ко-многим". Если посмотреть на мир вокруг себя, то мы сможем найти подобные модели. Наиболее распространенный и хрестоматийный пример - учеба в университете, где различное количество студентов может посещать различное количество дисциплин. И при этом у нас может возникнуть необходимость, как вести учет студентов по конкретной дисциплине, так и вести учет различных дисциплин для конкретного студента. Попробуем смоделировать данную ситуацию в приложении ASP.NET MVC 5.
Во-первых, создадим новый проект. Первым делом нам надо создать модели. Итак, у нас есть две модели - студент и курс университетской дисциплины. Сначала добавим модель Student со следующим содержанием:
public class Student { public int Id { get; set; } public string Name { get; set; } public string Surname { get; set; } public virtual ICollection<Course> Courses { get; set; } public Student() { Courses = new List<Course>(); } }
И также добавим модель Course:
public class Course { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<Student> Students { get; set; } public Course() { Students = new List<Student>(); } }
Модели довольно простые за исключением виртуальных свойств - Students и Courses - благодаря этим свойствам и будет происходить связь многие-ко-многим.
Следующий этап - создание контекста данных. Добавим в проект следующий класс StudentsContext:
public class StudentsContext : DbContext { public DbSet<Student> Students { get; set; } public DbSet<Course> Courses { get; set; } public StudentsContext() : base("DefaultConnection") {} protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Course>().HasMany(c => c.Students) .WithMany(s => s.Courses) .Map(t => t.MapLeftKey("CourseId") .MapRightKey("StudentId") .ToTable("CourseStudent")); } }
Во-первых, чтобы использовать строку подключения по умолчанию, установим для нее контекст данных в конструкторе: public StudentsContext() : base("DefaultConnection")
Дальше идет самое интересное. В создаваемой базе данных все данные о студентах будут храниться в таблице Students, а данные о университетских курсах - в таблице Courses. Но эти таблица должны быть как-то связаны связью многие-ко-многим. И эту связь обеспечит еще одна таблица, которая будет называться CourseStudent.
Для построения этой таблицы мы переопределяем метод OnModelCreating
, в котором с помощью объекта modelBuilder создаем новую таблицу и
определяем ее поля. Одно ее поле - CourseId - будет ссылаться на таблицу Courses и хранить в себе id курса. А второе поле - StudentId - будет ссылаться на
таблицу студентов и хранить id студента. В итоге у нас получится набор пар id курса - id студента, благодаря этому мы сможем определить связь
многие-ко-многим.
И теперь проинициализируем базу данных начальными данными. Добавим в проект следующий класс:
public class CourseDbInitializer : DropCreateDatabaseAlways<StudentsContext> { protected override void Seed(StudentsContext context) { Student s1 = new Student { Id = 1, Name = "Егор", Surname = "Иванов" }; Student s2 = new Student { Id = 2, Name = "Мария", Surname = "Васильева" }; Student s3 = new Student { Id = 3, Name = "Олег", Surname = "Кузнецов" }; Student s4 = new Student { Id = 4, Name = "Ольга", Surname = "Петрова" }; context.Students.Add(s1); context.Students.Add(s2); context.Students.Add(s3); context.Students.Add(s4); Course c1 = new Course { Id = 1, Name = "Операционные системы", Students = new List<Student>() { s1, s2, s3 } }; Course c2 = new Course { Id = 2, Name = "Алгоритмы и структуры данных", Students = new List<Student>() { s2, s4 } }; Course c3 = new Course { Id = 3, Name = "Основы HTML и CSS", Students = new List<Student>() { s3, s4, s1 } }; context.Courses.Add(c1); context.Courses.Add(c2); context.Courses.Add(c3); base.Seed(context); } }
Обратите внимание, как обеспечивается связь между курсами и студентами: мы просто добавляем набор созданных студентов в коллекцию Students для каждого курса. И чтобы все это заработало, добавим в файл Global.asax.cs в метод Application_Start следующую строчку:
Database.SetInitializer(new CourseDbInitializer());
Создадим контроллер. Так как вывод просто таблицы как курсов, так студентов не представляет сложности, то мы его разибрать не будем. Нас интересует получение связанных данных. Поэтому добавим контроллер HomeController и определим в нем следующий метод Details, который будет выводить информацию по конкретному студенту:
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Web; using System.Web.Mvc; using TeamMvc4.Models; namespace TeamMvc4.Controllers { public class HomeController : Controller { private StudentsContext db = new StudentsContext(); public ActionResult Index() { return View(db.Students.ToList()); } public ActionResult Details(int id = 0) { Student student = db.Students.Find(id); if (student == null) { return HttpNotFound(); } return View(student); } protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); } } }
Метод Details представляет обычный метод получения информации по объекту Student. И благодаря определению в модели Student виртуального свойства Courses, все связанные с данным студентом университетские курсы будут автоматически подключаться. Теперь добавим представление Details.cshtml:
@using TeamMvc4.Models @model Student @{ ViewBag.Title = "Details"; } <fieldset> <legend>Информация о студенте</legend> <div class="display-label"><b>Имя</b></div> <div class="display-field"> @Html.DisplayFor(model => model.Name) </div> <div class="display-label"><b>Фамилия</b></div> <div class="display-field"> @Html.DisplayFor(model => model.Surname) </div> <div class="display-label"><b>Курсы</b></div> <ul> @foreach (Course c in Model.Courses) { <li>@c.Name</li> } </ul> </fieldset>
Поскольку к модели автоматически цепляются связаные данные, то мы их можем получить через Model.Courses
. Так как у нас в базе данных
уже есть начальные данные, то мы можем получить информацию по первому студенту, отправив запрос Home/Details/1:
И также если мы посмотрим на базу данных, то обнаружим в ней, как и ожидалось, три таблицы. Связующая таблица CourseStudent будет иметь следующее содержание:
То есть все те же данные, которые мы указали для начальной инициализации базы данных.