Модели со связью многие-ко-многим

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core

Последнее обновление: 31.10.2015

Кроме моделей со связью один-ко-одному и один-ко-многим, которые были рассмотрены в прошлых темах, модели по типу "многие-ко-многим". Если посмотреть на мир вокруг себя, то мы сможем найти подобные модели. Наиболее распространенный и хрестоматийный пример - учеба в университете, где различное количество студентов может посещать различное количество дисциплин. И при этом у нас может возникнуть необходимость, как вести учет студентов по конкретной дисциплине, так и вести учет различных дисциплин для конкретного студента. Попробуем смоделировать данную ситуацию в приложении 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 будет иметь следующее содержание:

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

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850