Модели со сложной структурой

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

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

Ранее мы использовали относительно простые модели Book и Purchase. Но в реальных приложениях большинство моделей, как правило, оказываются гораздо сложнее по структуре. Например, создадим две следующие модели, представляющие футболиста и футбольную команду:

public class Player
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Position { get; set; }

    public int? TeamId { get; set; }
    public Team Team { get; set; }
}

public class Team
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Coach { get; set; }

    public ICollection<Player> Players { get; set; }
	public Team()
	{
		Players = new List<Player>();
	}
}

Кроме стандартных свойств типа string класс Player имеет свойство Team, которое определяет принадлежность футболиста к определенной команде. Свойство Team в данном случае является навигационным свойством. Благодаря навигационному свойству мы можем извлекать связанные с объектом данные из БД. Но для этого надо также установить внешний ключ.

Внешний ключ состоит из двух свойств: навигационного и обычного. Навигационное мы рассмотрели выше. А обычное должно принимать одно из следующих вариантов имени:

  • Имя_навигационного_свойства+Имя ключа из связанной таблицы - в нашем случае имя навигационного свойства Team, а ключа из модели Team - Id, поэтому в нашем случае свойство называется TeamId.

  • Имя_класса_связанной_таблицы+Имя ключа из связанной таблицы - в нашем случае класс Team, а ключа из модели Team - Id. И здесь опять же получается TeamId.

Теперь создадим контекст данных, использующий модели:

public class SoccerContext : DbContext
{
    public DbSet<Player> Players { get; set; }
    public DbSet<Team> Teams { get; set; }
}

Теперь посмотрим, как бы это все располагалось в БД. Например, создадим базу данных SoccerInfo.mdf. Пусть для хранения моделей Player и Team определены соответственно в таблицах Players и Teams.

Определение таблицы Teams, которая будет хранить объекты модели Team, выглядит следующим образом:

А таблица Players будет иметь следующую структуру:

В отличие от таблицы Teams здесь мы также задаем внешний ключ - свойство TeamId теперь будет ссылаться на поле Id из таблицы Teams.

Чтобы задать внешний ключ, мы добавляем в панели SQL внизу под дизайнером таблицы следующую строку:

CONSTRAINT [FK_Players_Teams] FOREIGN KEY ([TeamId]) REFERENCES [Teams]([Id]) ON DELETE SET NULL

Это обычное выражение языка SQL, которое связывает столбцы двух таблиц.

Последняя часть этого выражения (ON DELETE SET NULL) указывает, что при удалении объекта из таблицы Teams, свойству TeamId, которое ссылалось на удаленный объект, будет присвоено значение null.

Это надо, чтобы у нас игроки при удалении команд не относились больше к удаленным командам. Однако мы можем задать и другое действие, например, при удалении команды удалить всех ее игроков. Для этого нам надо написать ON DELETE CASCADE.

Теперь после определения таблиц наполним их некоторыми начальными данными. К примеру добавим некоторые данные в таблицу Teams:

И в таблицу Players (где столбец TeamId содержит некоторое существующее значение из столбца Id таблицы Teams):

Теперь перейдем к созданию логики приложения. Добавим в приложение контроллер и определим в нем вывод всех игроков на страницу:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using NavigationProperty.Models;
using System.Data.Entity;

namespace NavigationProperty.Controllers
{
    public class HomeController : Controller
    {
        SoccerContext db = new SoccerContext();

        // Выводим всех футболистов
        public ActionResult Index()
        {
            var players = db.Players.Include(p => p.Team);
            return View(players.ToList());
        }
	}
}

Теперь с помощью метода Include фреймворк подгружает для каждого игрока команду, связанную с данным игроком через внешний ключ. И создадим представление Index.cshtml, которое будет выводить всех игроков:

@model IEnumerable<NavigationProperty.Models.Player>
@{
    ViewBag.Title = "Каталог игроков";
}

<h2>Каталог игроков</h2>
<p>
    @Html.ActionLink("Добавить игрока", "Create")
</p>
<table>
    <tr>
        <th>Имя игрока</th>
        <th>Возраст</th>
        <th>Позиция на поле</th>
        <th>Команда</th>
        <th></th>
    </tr>

    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Age)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Position)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Team.Name)
            </td>
            <td>
                @Html.ActionLink("Редактировать", "Edit", new { id = item.Id }) |
                @Html.ActionLink("Удалить", "Delete", new { id = item.Id })
            </td>
        </tr>
    }
</table>
<p>
    @Html.ActionLink("Каталог команд", "ListTeams")
</p>

Так как в методе контроллера с помощью метода Include ко всем моделям Player подключается свой объект Team по навигационному свойству TeamId, то то в представлении мы можем получить этот связанный объект Team и использовать его свойства, например, получить item.Team.Name для отображения имени команды.

Подобным образом можно вывести список команд. Но там все просто и не так интересно. Зато у нас в модели Team свойство Players, которое призвано хранить связанных с командой игроков. Используем его. Например, выведем все данные о команде, в том числе о ее игроках. Вначале добавим в контроллер следующий метод:

public ActionResult TeamDetails(int? id)
{
    if (id == null)
    {
        return HttpNotFound();
    }
    Team team = db.Teams.Include(t=>t.Players).FirstOrDefault(t=>t.Id==id);
    if (team == null)
    {
        return HttpNotFound();
    }
    return View(team);
}

Во-первых, чтобы обработать ввод при отсуствии передаваемого значения, в качестве параметра используем int? id. Во-вторых, мы подгружаем всех игроков, связанных с командой, в выражении db.Teams.Include(t=>t.Players).FirstOrDefault(t=>t.Id==id).

Ну и представление TeamDetails.cshtml для отображения данных о команде могло бы выглядеть так:

@using NavigationProperty.Models
@model Team

@{
    ViewBag.Title = "Команда " + @Model.Name;
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<div>
    <h4>Команда @Model.Name</h4>
	<hr />
    <dl>
        <dt>Название</dt>

        <dd>
            @Html.DisplayFor(model => model.Name)
        </dd>

        <dt>Тренер</dt>

        <dd>
            @Html.DisplayFor(model => model.Coach)
        </dd>

        <dt>Игроки</dt>

        <dd>
            <ul>
                @foreach (Player player in Model.Players)
                {
                    <li>@player.Name (@player.Position)</li> 
                }
            </ul>
        </dd>
    </dl>
</div>
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850