Предыдущие части.
Уроки по FluentNHibernate c ASP.NET MVC и SQL Server. Часть 1
Часть 2. Создание классов, маппингов и заполнение БД

В предыдущей части было рассмотрено создание классов и маппинг-классов к ним. В 3 кратких таблицах были представлены маппинги связей один-к-одному, один-ко-многим, многие-ко-многим. Заполнили таблицы в sql server'e данными. В этой статье отобразим данные из БД в представлении View. Вначале добавим изображения в проект.

Отображение данных из таблицы в представление
Прежде чем создавать представление, для начала добавим в приложение изображения. Создадим в папке Content папку img.


и добавим туда изображения, к примеру, обложки этих книг.
yadi.sk/a/OZVgEhJ1iXukT

Далее изменим контроллер, чтобы он отображал данные, связанные с книгой.

---код Lazyloading загрузки — ---Query Over---
    using (ISession session = NHibernateHelper.OpenSession()) {
       Genre genreAl = null;
	    Author authorAl = null;
	    Series seriesAl = null;
	    var books = session.QueryOver<Book>()
                        //Left Join с таблицей Genres 
			.JoinAlias(p => p.Genres, () => genreAl, JoinType.LeftOuterJoin)
			.JoinAlias(p => p.Authors, () => authorAl, JoinType.LeftOuterJoin)
			.JoinAlias(p => p.Series, () => seriesAl, JoinType.LeftOuterJoin)
                        //Убирает повторяющиеся id номера таблицы Book.
			.TransformUsing(Transformers.DistinctRootEntity).List();
	  return View(books);
    }


Небольшое объяснение
  1. var books = session.QueryOver<Book>() — подобно выполнению скрипта SQL: Select * From Book;
  2. .JoinAlias(p => p.Genres, () => genreAl, JoinType.LeftOuterJoin) — подобно выполнению скрипта SQL:
    SELECT *FROM Book
    inner JOIN Book_Genre ON book.id = Book_Genre.Book_id
    LEFT JOIN Genre ON Book_Genre.Genre_id = Genre.id
  3. .TransformUsing(Transformers.DistinctRootEntity) — Подобно выполнению скрипта SQL: SELECT distinct Book.Id..., (убирает дублирующие записи с одинаковыми id)


Виды объединений
.JoinAlias(p => p.Genres, () => genreAl, JoinType.LeftOuterJoin)
  1. LeftOuterJoin — выбирает все записи из левой таблицы (Book), а затем присоединяет к ним записи правой таблицы (Genre). Если не найдена соответствующая запись в правой таблицы, отображает её как Null
  2. RightOuterJoin действует в противоположность LEFT JOIN — выбирает все записи из правой таблицы (Genre), а затем присоединяет к ним записи левой таблицы (Book)
  3. InnerJoin — выбирает только те записи из левой таблиц (Book) у которой есть соответствующая запись из правой таблицы (Genre), а затем присоединяет к ним записи из правой таблицы


Далее переделаем страницу Index.cshtml следующим образом.

Код представления
 @model IEnumerable<BibliotecaTutor.Models.Book>
@{    Layout = null; }
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
      <title>Index</title>
       <style> th,td {border: 1px solid; } </style>
</head>
<body>
    <p>@Html.ActionLink("Create New", "Create")</p>
       <table>
             <tr>
                    <th>@Html.DisplayNameFor(model => model.Image)</th>
                    <th>@Html.DisplayNameFor(model => model.Name)</th>
                    <th>@Html.DisplayNameFor(model => model.MfRaiting)</th>
                    <th>@Html.DisplayNameFor(model => model.Mind)</th>
                    <th>@Html.DisplayNameFor(model => model.Series)</th>
                    <th>@Html.DisplayNameFor(model => model.Genres)</th>
                    <th>@Html.DisplayNameFor(model => model.Authors)</th>
                    <th>Операции</th>
             </tr>

             @foreach (var item in Model) {
             <tr> 
                    <td> <img src="~/Content/img/@item.Image" /></td>//Ссылка на рисунки
                    <td>@Html.DisplayFor(modelItem => item.Name)</td>
                    <td>@Html.DisplayFor(modelItem => item.MfRaiting)</td>
                    <td>@Html.DisplayFor(modelItem => item.Mind.MyMind)</td>
                    @{string strSeries = item.Series!=null?item.Series.Name:null;}
                    <td>@Html.DisplayFor(modelItem => strSeries)</td>
                    <td>
                            @foreach (var genre in item.Genres) {
                                string strGenre = genre!= null ? genre.Name : null;
                                @Html.DisplayFor(modelItem => strGenre) <br />
                            }
                    </td>
                    <td>
                          @foreach (var author in item.Authors) {
                                  string strAuthor = author != null ? author.Name : null;
                                 @Html.DisplayFor(modelItem => strAuthor) <br />
                          }
                    </td>
                  <td>
                 @Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
                 @Html.ActionLink("Details", "Details", new { id = item.Id }) |
                 @Html.ActionLink("Delete", "Delete", new { id = item.Id })
                 </td>
             </tr>
             }
       </table>
</body>
</html>



После этих всех действий запускаем проект и видим вот такую таблицу:


Маппинг для классов, у которых есть наследование.
А как маппить классы у которых есть наследование? Допустим, имеем такой пример:
 //Класс Двумерных фигур
public class TwoDShape {
    //Ширина
    public virtual int Width { get; set; }
    //Высота
    public virtual int Height { get; set; }
}
//Класс треугольник
public class Triangle : TwoDShape {
    //Идентификационный номер
    public virtual int Id { get; set; }
    //Вид треугольника
    public virtual string Style { get; set; }
}


В принципе, ничего сложного в этом маппинге нет, мы просто создадим один маппинг для производного класса, то есть таблицы Triangle.
 //Маппинг треугольника
public class TriangleMap : ClassMap<Triangle> {
       public TriangleMap() {
             Id(x => x.Id);
             Map(x => x.Style);
             Map(x => x.Height);
             Map(x => x.Width);
       }
}

После запуска приложения, в БД Biblioteca появится следующая (пустая) таблица

Комментарии (8)


  1. gfhfk
    20.08.2015 12:03

    В первую очередь выражаю благодарность за уроки, очень удобно когда такие небольшие…
    Вопрос: можно ли при выборке со связаными таблицами выбирать не все поля, а только те, которые необходимо (У автором выбрать только название)?
    В больших проектах выбирать все поля со всех таблиц накладно


    1. QSandrew
      20.08.2015 12:31

      Я правильно понял, вы хотите выбрать записи из таблицы Book и Author, принадлежащие определенному автору, а остальные таблицы проигнорировать?


      1. gfhfk
        20.08.2015 13:44

        Нет. Запрос пусть будет тот же. Но со связанных таблиц выбирать только некоторые поля.


  1. CapitanBlood
    20.08.2015 12:38
    +1

    не совсем не понимаю, зачем такой огород? если вы хотите избавиться от ленивой загрузки (не на всей сущности, а только в определенных запросах), то почему не воспользоваться fetch (thenFethc, fethcAll, etc) и не городить такой огород. Существующий код сводит на нет все прелести orm — мы все свзяи формируем вручную, и появляется сотня мест, где мы можем опечататься.

    а CreateSQLQuery — это уже вообще хороший сигнал к тому, что нужно задуматься, а все ли вы делаете верно. в примерах о нем упоминать вообще не стоит. Пример — это способ показать новичкам способ использования фреймворка/библиотеки, но если сразу показать костыль, то именно этим костылем и будут подпираться все дыры


    1. QSandrew
      20.08.2015 13:04

      то почему не воспользоваться fetch (thenFethc, fethcAll, etc)
      — а слона-то я не приметил…
      Статья будет изменена, спасибо за замечание.


    1. QSandrew
      20.08.2015 13:32

      CreateSQLQuery — в примерах о нем упоминать вообще не стоит

      Тут я не согласен. Приведу пример. В таблице есть функция, которую иногда меняют. А мне требуется использовать эту функцию. Мне проще использовать CreateSQLQuery, чем при каждом изменении этой функции менять свой код. Знать об этом методе стоит, но применять его только в самых крайних случаях.


      1. justmara
        20.08.2015 15:57

        ORM вообще и NHibernate в частности — инструменты, позволяющие абстрагировать логику от внутренней структуры и особенностей реализации хранилища данных (базы в данном случае). Как только вы задействуете ручную генерацию запросов — это значит, что вам этот NHibernate не нужен и можете сразу писать напрямую Sql/Oracle/MySqlConnection. Работая с IDataReader'ами вы и то более надёжную абстракцию поимеете, чем этот зоопарк из маппингов, fetch/list/sqlquery…


  1. QSandrew
    21.08.2015 11:40

    Статья изменена. Добавлен материал про виды загрузок eager и lazy loading