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

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

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

Ранее использованные нами модели Book и Purchase имели довольно простую структуру - одни простые свойства типа int или string. Например, рассмотрим следующие две модели:

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 IEnumerable<Player> Players { get; set; }
}

Здесь класс 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, выглядит следующим образом:

Скрипт создания таблицы:

CREATE TABLE [dbo].[Teams] (
    [Id]    INT NOT NULL  PRIMARY KEY IDENTITY,
    [Name]  NVARCHAR (50) NOT NULL,
    [Coach] NVARCHAR (50) NOT NULL
);

Тогда чтобы проецировать модель Player на таблицу Players в базе данных, нам надо задать следующее определение столбцов таблицы:

Скрипт создания таблицы:

CREATE TABLE [dbo].[Players] (
    [Id]       INT NOT NULL  PRIMARY KEY IDENTITY,
    [Name]     NVARCHAR (50) NOT NULL,
    [Age]      INT           NOT NULL,
    [Position] NVARCHAR (50) NOT NULL,
    [TeamId]   INT           NULL,
    CONSTRAINT [FK_Players_Teams] FOREIGN KEY ([TeamId]) REFERENCES [Teams] ([Id]) ON DELETE SET NULL
);

Кроме того, здесь мы задаем внешний ключ - свойство 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;
using System.Data;

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 получили связанный с игроком объект Team по навигационному свойству TeamId, то при передаче в контроллер всех объектов Player к ним также цепляются связанные с ними объекты Team, поэтому мы можем вполне использовать в представлении свойство item.Team.Name для получения имени команды.

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

public ActionResult TeamDetails(int? id)
{
    if (id == null)
    {
        return HttpNotFound();
    }
    Team team = db.Teams.Find(id);
    if (team == null)
    {
        return HttpNotFound();
    }
    team.Players = db.Players.Where(m=> m.TeamId== team.Id);
    return View(team);
}

Во-первых, чтобы обработать ввод при отсутствии передаваемого значения, в качестве параметра используем int? id. Во-вторых, мы подгружаем всех игроков, связанных с командой, в выражении team.Players = db.Players.Where(m=> m.TeamId== team.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