📜 ⬆️ ⬇️

Part 2. Creating classes, mappings and filling database

CLASSES AND MAPPINGS

Lessons in FluentNHibernate c ASP.NET MVC and SQL Server. Part 1
Part 3. Displaying data from a table (Operation LIST)

In the previous part, the types of relationships were considered (one-to-one, one-to-many, many-to-many), as well as one class Book and its mapping class BookMap. In the second part, we will update the Book class, create the other classes and links between them, as was shown in the previous chapter in the Database Chart located above the subtitle 1.3.1 Links.
')

Code of classes and mappings (With comments)
Class Book

public class Book { //  public virtual int Id { get; set; } // public virtual string Name { get; set; } // public virtual string Description { get; set; } //   public virtual int MfRaiting { get; set; } //  public virtual int PageNumber { get; set; } //   public virtual string Image { get; set; } //   (  !) public virtual DateTime IncomeDate { get; set; } // (--) // ISet   IList?    (IList)     JOIN ,        JOIN,       ISet public virtual ISet<Genre> Genres { get; set; } // (--) public virtual Series Series { get; set; } //   (--) private Mind _mind; public virtual Mind Mind { get { return _mind ?? (_mind = new Mind()); } set { _mind = value; } } // (--) public virtual ISet<Author> Authors { get; set; } // ,   null  . public Book() { //  (       --  ,     ,   ) Genres = new HashSet<Genre>(); Authors = new HashSet<Author>(); } } //  Book public class BookMap : ClassMap<Book> { public BookMap() { Id(x => x.Id); Map(x => x.Name); Map(x => x.Description); Map(x => x.MfRaiting); Map(x => x.PageNumber); Map(x => x.Image); Map(x => x.IncomeDate); // -- HasManyToMany(x => x.Genres) //  All -   ,   ,   ////    .Cascade.SaveUpdate() //         Genre! .Table("Book_Genre"); HasManyToMany(x => x.Authors) .Cascade.SaveUpdate() .Table("Book_Author"); //    References(x => x.Series); // --.  . HasOne(x => x.Mind).Cascade.All().Constrained(); } } 


Class Author

  public class Author { public virtual int Id { get; set; } //- public virtual string Name { get; set; } // public virtual string Biography { get; set; } // public virtual ISet<Book> Books { get; set; } //  public Author() { Books=new HashSet<Book>(); } } //  public class AuthorMap : ClassMap<Author> { public AuthorMap() { Id(x => x.Id); Map(x => x.Name); Map(x => x.Biography); // -- HasManyToMany(x => x.Books) //  All -   ,   ,   //    .Cascade.All() //  .    (Book)     . .Inverse() //         Book! .Table("Book_Author"); } } 


Class Genre

  public class Genre { public virtual int Id { get; set; } //  public virtual string Name { get; set; } //   public virtual string EngName { get; set; } // public virtual ISet<Book> Books { get; set; } //  public Genre() { Books=new HashSet<Book>(); } } //  public class GenreMap : ClassMap<Genre> { public GenreMap() { Id(x => x.Id); Map(x => x.Name); Map(x => x.EngName); // -- HasManyToMany(x => x.Books) //  All -   ,   ,   //    .Cascade.All() //  .    (Book)     . .Inverse() //         Book! .Table("Book_Genre"); } } 


Class Opinion:

  public class Mind { public virtual int Id { get; set; } //  public virtual string MyMind { get; set; } //  public virtual string MindFantLab { get; set; } // public virtual Book Book { get; set; } } // ind public class MindMap:ClassMap<Mind> { public MindMap() { Id(x => x.Id); Map(x => x.MyMind); Map(x => x.MindFantLab); //    HasOne(x => x.Book); } } 


Class Cycle (Series):

  public class Series { public virtual int Id { get; set; } public virtual string Name { get; set; } //  IList,   ISet,    Book, Series      ,     ISet public virtual IList<Book> Books { get; set; } // . public Series() { Books = new List<Book>(); } } public class SeriesMap : ClassMap<Series> { public SeriesMap() { Id(x => x.Id); Map(x => x.Name); // -- HasMany(x => x.Books) ////  .    (Book)     . .Inverse() } } 



Little explanation
public virtual ISet <Genre> Genres {get; set; }
public virtual ISet <Author> Authors {get; set; }

Why ISet <Class>, and not, for example, familiar to many IList <Class>? If you use IList instead of ISet, and try to start the project, we will not notice any special differences (Tables and classes will be created). But when we both have the Genre table and the Authors table for the Book class LeftJoin, and we are still trying to derive non-repeating entries from the Book (Distinct Book.Id) table into the View, Nhibernate throws an exception and an error.
Cannot simultaneously fetch multiple bags.
In such cases, we use ISet, especially the sets for this purpose are intended (ignore duplicate entries).

Attitude many-to-many.

BookAuthor
HasManyToMany (x => x.Genres)
.Cascade.SaveUpdate ()
.Table ("Book_Author");
HasManyToMany (x => x.Books)
.Cascade.All ()
.Inverse ()
.Table ("Book_Author");

In NHibernate there is a concept, the "main" table. Although the many-to-many relationships between the “Book” and “Author” tables are equivalent (The author may have many books, the book may have many authors), Nhibernate requires the programmer to specify a table that is saved second (has a method. inverse ()), that is, the entry in the Book table will be created / updated / deleted first, and only then in the Author table.
Cascade.All means performing cascading operations with save-update and delete. That is, when an object is saved, updated, or deleted, all dependent objects are checked and created / updated / added (Ps. You can add Cascade.All -> .Cascade.SaveUpdate (). Cascade.Delete ())
Method .Table ("Book_Author"); creates an “intermediate” table “Book_Author” in the database.

A many-to-one, one-to-many relationship.
BookSeries
References (x => x.Series) .Cascade.SaveUpdate ();
HasMany (x => x.Books)
.Inverse ();

The References method is applied on the many-to-one side, on the other side, the one-to-many method is HasMany.

One-to-one relationship
BookMind
HasOne (x => x.Mind) .Cascade.All (). Constrained ();
HasOne (x => x.Book);

The .Constrained () method tells NHibernate that a record from the Mind table must match the record from the Book table (the Mind table id must be equal to the Book table id)

If you start the project now and see the Bibilioteca database, new tables with already formed links will appear.

Next, fill in the created table data ...
To do this, we will create a test application that will save the data in the database, update and delete it, changing the HomeController as follows (Comment on the unnecessary parts of the code):
 public ActionResult Index() { using (ISession session = NHibernateHelper.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { //,  var createBook = new Book(); createBook.Name = "Metro2033"; createBook.Description = " "; createBook.Authors.Add(new Author { Name = "" }); createBook.Genres.Add(new Genre { Name = " " }); createBook.Series = new Series { Name = "" }; createBook.Mind = new Mind { MyMind = " " }; session.SaveOrUpdate(createBook); // ( ) //var series = session.Get<Series>(1); //var updateBook = session.Get<Book>(1); //updateBook.Name = "Metro2034"; //updateBook.Description = ""; //updateBook.Authors.ElementAt(0).Name = ""; //updateBook.Genres.ElementAt(0).Name = ""; //updateBook.Series = series; //updateBook.Mind.MyMind = "11111"; //session.SaveOrUpdate(updateBook); // ( ) //var deleteBook = session.Get<Book>(1); //session.Delete(deleteBook); transaction.Commit(); } Genre genreAl = null; Author authorAl = null; Series seriesAl = null; Mind mindAl = 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) .JoinAlias(p => p.Mind, () => mindAl, JoinType.LeftOuterJoin) //  id   Book. .TransformUsing(Transformers.DistinctRootEntity).List(); return View(books); } } 


Little explanation
  1. var books = session.QueryOver <Book> () is similar to running the SQL script: Select * From Book ;
  2. .JoinAlias ​​(p => p.Genres, () => genreAl, JoinType.LeftOuterJoin) is similar to running the SQL script:
    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) - Like executing the SQL script: SELECT distinct Book.Id ... , (removes duplicate entries with the same id)


Types of associations
.JoinAlias ​​(p => p.Genres, () => genreAl, JoinType.LeftOuterJoin)
  1. LeftOuterJoin - selects all entries from the left table ( Book ), and then appends to them the entries of the right table ( Genre ). If the corresponding entry in the right table is not found, displays it as Null.
  2. RightOuterJoin acts as opposed to LEFT JOIN - selects all the entries from the right table ( Genre ), and then appends the left table entries to them ( Book )
  3. InnerJoin - selects only those records from the left table ( Book ) which has a corresponding record from the right table ( Genre ), and then appends to them records from the right table


Change the view as follows:
Index view
 @model IEnumerable<NhibernateMVC.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.Name)</th> <th>@Html.DisplayNameFor(model => model.Mind)</th> <th>@Html.DisplayNameFor(model => model.Series)</th> <th>@Html.DisplayNameFor(model => model.Authors)</th> <th>@Html.DisplayNameFor(model => model.Genres)</th> <th></th> </tr> @foreach (var item in Model) { <tr> <td>@Html.DisplayFor(modelItem => item.Name)</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 author in item.Authors) { string strAuthor = author != null ? author.Name : null; @Html.DisplayFor(modelItem => strAuthor) <br /> } </td> <td> @foreach (var genre in item.Genres) { string strGenre = genre!= null ? genre.Name : null; @Html.DisplayFor(modelItem => strGenre) <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> 


After checking all the operations one by one, we note that:


Mapping for classes that have inheritance.
And how mappit classes that have inheritance? Suppose we have the following example:
  //   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; } } 


In principle, there is nothing difficult in this mapping, we simply create one mapping for the derived class, that is, the Triangle table.
  //  public class TriangleMap : ClassMap<Triangle> { public TriangleMap() { Id(x => x.Id); Map(x => x.Style); Map(x => x.Height); Map(x => x.Width); } } 

After starting the application, the following (empty) table will appear in the Biblioteca database

Source: https://habr.com/ru/post/264961/


All Articles