Show child data in View MVC - c#

How to show child data in view here I have two models Book (parent) and Author (child).
I want to show author name on view but in Book model I have Author object with null reference, but AuthorId has value 1.
I succeeded to get child values in controller when I got Book data with DbContext object I search for Author data, I don't know is this correct way because I am new with ASP.NET MVC or there exists better solution to connect data between parent and child data?
Here is my controller and model classes.
public ActionResult Book(int id)
{
var book = dbContext.Books.SingleOrDefault(b => b.ID == id);
if(book.AuthorId != null)
{
book.Author = dbContext.Authors.SingleOrDefault(a => a.ID == book.AuthorId);
}
return View(book);
}
Book model class.
public class Book
{
[Key]
public int ID { get; set; }
public string Title { get; set; }
public decimal Score { get; set; }
public Format Format { get; set; }
public Language Language { get; set; }
[ForeignKey("Author")]
public int? AuthorId { get; set; }
public Author Author { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public decimal Discount { get; set; }
}
And this child model Author
public class Author
{
[Key]
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Book> Books { get; set; }
}
EDIT:
Solution is to use Include extension method which can be found in this namespace System.Data.Entity.
public ActionResult Book(int id)
{
var book = dbContext.Books
.Include(a => a.Author)
.Include(p => p.ProductDetails)
.SingleOrDefault(b => b.ID == id);
return View(book);
}

Code does not seem to have any errors. Make sure your Database Table Column names match exactly with Property name. And try different ways of adding forign key in EF.
Adding Foreign Key

Related

Can't get category's books even though the Book entity has a category foreign key?

I am new to asp.net so easy on me :D, I am builiding a simple CRUD app, I managed to get the lists and routing correctly, but now I want to get a category's book.
So that's my Book & Category Entities :
public class Book
{
[Key]
public int bookId { get; set; }
public string bookName { get; set; }
public double bookPrice { get; set; }
public string bookImageUrl { get; set; }
[ForeignKey("Category")]
public int categoryId { get; set; }
public Category category { get; set; }
}
public class Category
{
[Key]
public int categoryId { get; set; }
public string categoryName { get; set; }
public string categoryDescription { get; set; }
public List<Book> books { get; set; }
}
This is my category repository, the second method is the one, I am including the books :
public class CategoryRepository : ICategoryRepository
{
private readonly AppDbContext _appDbContext;
public CategoryRepository(AppDbContext appDbContext)
{
_appDbContext = appDbContext;
}
public IEnumerable<Category> getAllCategories()
{
return _appDbContext.Categories;
}
public Category getCategoryById(int categoryId)
{
return _appDbContext.Categories.Include(category => category.books).FirstOrDefault(category => category.categoryId == categoryId);
}
}
This is my CategoryController even though I don't the probleme is here :
private readonly ICategoryRepository _categoryRepository;
public CategoryController(ICategoryRepository categoryRepository)
{
_categoryRepository = categoryRepository;
}
public IActionResult categoryBooks(int idCategory)
{
Trace.WriteLine("this " + idCategory);
var booksListViewModel = new BooksListViewModel();
booksListViewModel.Books = _categoryRepository.getCategoryById(idCategory).books;
return View(booksListViewModel);
}
}
I have one book & one category in the database and that's it :
Any ideas ? Any guiding ?
Any help would be much appreciated.
Since you are asking for a first or default
return _appDbContext.Categories.Include(category => category.books).FirstOrDefault(category => category.categoryId == categoryId);
it returns the first category it found. This is why you have only one record. It is a relational data base. Each record has one bookId and one categoryId and it takes the first record where where is true.
if you want a list books with category you have change query to this
public IEnumerable<Book> getBooksByCategoryId(int categoryId)
{
return _appDbContext.Books.Include(c => c.category).Where(category => category.categoryId == categoryId).ToArray();
}

Entity Framework REST endpoint to get all entites that have another entity

I'm having trouble figuring out how I can return all books that have a rating. For every book, a rating may apply (one-to-zero-or-one relationship).
Book model class:
public class Book
{
[Key]
[NotNull]
[DisplayName("isbn")]
public string Isbn { get; set; }
[NotNull]
[DisplayName("title")]
[MaxLength(255)]
[MinLength(1)]
public string Title { get; set; }
[NotNull]
[DisplayName("author")]
[MaxLength(255)]
[MinLength(1)]
public string Author { get; set; }
[NotNull]
[DisplayName("year_of_publication")]
public int yearOfPublication { get; set; }
[NotNull]
[DisplayName("publisher")]
[MaxLength(255)]
public string publisher { get; set; }
[JsonIgnore]
public virtual Rating Rating { get; set; }
}
Rating class:
public class Rating
{
[NotNull]
[DisplayName("rating_seq")]
public int Id { get; set; }
[NotNull]
[MaxLength(13,ErrorMessage = "ISBN is too long!")]
[MinLength(1)]
[ForeignKey("Book")]
public string Isbn { get; set; }
[NotNull]
[DisplayName("book_rating")]
public int rating { get; set; }
[JsonIgnore]
public virtual Book Book { get; set; }
}
Basically, I should return a list of books that have a rating. Right now I'm thinking something along the lines of:
[HttpGet("/ratings")]
public async Task<ActionResult<IEnumerable<Book>>> GetBooksWithRating()
{
var books = await _context.Book
.Include(l => l.Rating)
.Where(s => s.Isbn == _context.Rating.Isbn) //not accessible
.ToListAsync();
if (books == null)
{
return NotFound();
}
return books;
}
Obviously this is false, but any help on how to correct it would be greatly appreciated!
Did you try something like this
var books = await _context.Book
.Include(b => b.Rating)
.Where(b => b.Rating != null)
.ToListAsync();
Here we tell EF to Include the Rating navigation property on all the books we retrieve, but only return those books if the Rating is not null - which would indicate a book without a Rating

Many-To-Many relationship in cshtml not getting data

Trying to connected 2 models with Many-To-many relationship and display the data in cshtml using viewModel.
Model Book (data annotations removed for simplicity):
public int Id { get; set; }
public string Title { get; set; }
public int PublisherId { get; set; }
public Publisher Publisher { get; set; }
public double Cost { get; set; }
public int Code { get; set; }
public int InvNr { get; set; }
public string Description { get; set; }
public IList<BookAuthor> BookAuthors { get; set; }
and Author model (data annotations removed for simplicity):
public int Id { get; set; }
public string Name { get; set; }
public string Lastname { get; set; }
public IList<BookAuthor> BookAuthors { get; set; }
Since its a Many-To-Many relationship, created another model for these models BookAuthor (data annotations removed for simplicity):
public int BookId { get; set; }
public Book Book { get; set; }
public int AuthorId { get; set; }
public Author Author { get; set; }
Inside my DataAccess class i override ModelCreation (other tables removed for simplicity):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookAuthor>().HasKey(sc => new { sc.BookId, sc.AuthorId });
modelBuilder.Entity<BookAuthor>().HasOne(sc => sc.Book)
.WithMany(s => s.BookAuthors)
.HasForeignKey(sc => sc.BookId);
modelBuilder.Entity<BookAuthor>().HasOne(sc => sc.Author)
.WithMany(s => s.BookAuthors)
.HasForeignKey(sc => sc.AuthorId);
}
public DbSet<Author> Authors { get; set; }
public DbSet<Book> Books { get; set; }
public DbSet<BookAuthor> BookAuthors { get; set; }
Inserting the data from cshtml to database works fine.
The Problem.
Displaying the data returns a null value.
Inside the ViewModel (Other lines removed for simplicity) public IEnumerable<Book> Books { get; set; }, i give the Books variable data form my controller (Some lines removed for simplicity):
[HttpGet]
public ViewResult Index(int? Id)
{
BooksIndexViewModel viewModel = new BooksIndexViewModel()
{
Books = _booksRepository.GetAllBooks(),
};
return View(viewModel);
}
The _booksRepository.GetAllBooks() returns : return db.Books;
db stand for my database private readonly DataAccess db;
Inside the cshtml.
I used foreach loop foreach (var book in Model.Books) for Model.Books. displaying the title, cost, code and so on, works fine. But the problem is With the Author. typing book.BookAuthors returns null value.
If i understood correctly, book.BookAuthors should return a list therefor i tried using it inside the loop:
#foreach (var author in book.BookAuthors) { #author.Author.Name }
Tried #foreach (var author in book.BookAuthors.Where(u => u.BookId == book.Id).Select(u => u.Author)) { #author.Name } and few other similar attempts. The error ALWAYS occurs because of
book.BookAuthors - its a null.
WHY?!
Shouldn't calling book.BookAuthors automatically connect from book to author tables using BookAuthors table as a bridge?
IF inside the controller i do this:
IEnumerable<Book> books = _booksRepository.GetAllBooks();
IEnumerable<BookAuthor> bookAuthors = _booksRepository.GetAllBookAuthors();
foreach (var book in books)
foreach (var bookauth in bookAuthors)
{
}
(Yes the loop is empty) and pass it to my view model:
BooksIndexViewModel viewModel = new BooksIndexViewModel()
{
Books = books,
};
Then this #foreach (var author in book.BookAuthors) { #author.Author.Name} inside of a loop #foreach (var book in Model.Books) inside the cshtml works fine...
WHY?! How come looping thru all available Books and BookAuthors inside the loop and not even assigning any values do the work?

automapper Many-to-Many relation mapping

Let's say, in the BookDetails page (BookForDetailsDto) we also show the authors of that book (AuthorForListingDto). And moreover, I want to show this author list together with a little info (just the name and id) on the books (BookForAuthorListingDto) of each author.
I have a simple many-to-many relation consisting of Book, Author and BookAuthor objects.
public class Book {
public int Id { get; set; }
public string Name { get; set; }
public List<BookAuthor> Authors { get; set; }
}
public class Author {
public int Id { get; set; }
public string Name { get; set; }
public List<BookAuthor> Books { get; set; }
}
public class BookAuthor {
public int BookId { get; set; }
public Book Book { get; set; }
public int AuthorId { get; set; }
public Author Author { get; set; }
}
And I have also 3 DTOs (where I am stoping an infinite loop):
public class BookForDetailsDto {
public int Id { get; set; }
public string Name { get; set; }
public List<AuthorForListingDto> Authors { get; set; }
}
public class AuthorForListingDto {
public int Id { get; set; }
public string Name { get; set; }
public List<BookForAuthorListingDto> Books { get; set; }
}
public class BookForAuthorListingDto {
public int Id { get; set; }
public string Name { get; set; }
}
Having a configuration as the following:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Book, BookForDetailsDto>();
cfg.CreateMap<BookAuthor, AuthorForListingDto>();
cfg.CreateMap<AuthorForListingDto, BookForAuthorListingDto>();
});
I'd like to perform a mapping from Book to BookForDetailsDto like this.
BookForDetailsDto BookDto = mapper.Map<BookForDetailsDto>(book);
But as a result, I get System.NullReferenceException.
It seems like, just in the first level of mapping, AutoMapper cannot get Author information from BookAuthor object.
I am searching for a configuration option but with no luck. I should say I am a newbie with automapper and if there is a simple solution I appreciate.
Note: I saw a comment which goes like "it is not a good practice to have reference in one DTO to second DTO". But I cannot figure out how to do otherwise, because ,for example, for a clickable/navigatable child_object we need at least "a key and a display_name", so a child object of type List seems inevitable.
A new day with a new head...
I changed the mappings like the following and it works as expected:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Book, BookForDetailsDto>()
.ForMember(dto => dto.Authorss, opt => opt.MapFrom(x => x.Authors.Select(a => a.Author)));
cfg.CreateMap<BookAuthor, BookForAuthorListingDto >()
.ForMember(res => res.Id, opt => opt.MapFrom(dto => dto.Book.Id))
.ForMember(res => res.Name, opt => opt.MapFrom(dto => dto.Book.Name));
});

How to load DTO Within DTOs?

PROBLEM:
I am very new to EF and to LINQ, so please bear with me.
I am trying to create a EF6 model using the database first approach. Simply speaking, I have 2 database tables tblUser and tblMilkMan which have a foreign key relationship on the UserID column.
To avoid cyclic references and to shape the entity data I have created DTO classes for both the models.
I made the MilkManDTO class contain a reference to a UserDTO instance.(This is probably stupid, if so, please guide me to the right way).My aim is to be able to load a milkmen and the related User data
Anyway in my API call, when I try to load a MilkMan by ID, I do not know how to load the related UserDTO. I found examples online on how to load related Entities but not related DTOs.
DB Schema:
Models:
MilkMan Model and DTO:
namespace MilkMan.Models
{
using System;
using System.Collections.Generic;
public partial class tblMilkMan
{
public int RecordID { get; set; }
public int UserID { get; set; }
public bool IsMyTurn { get; set; }
public int RoundRobinOrder { get; set; }
public virtual tblUser tblUser { get; set; }
}
public class MilkManDTO
{
public int RecordID { get; set; }
public int UserID { get; set; }
public bool IsMyTurn { get; set; }
public int RoundRobinOrder { get; set; }
public virtual UserDTO User { get; set; }
}
}
User Model and DTO:
public partial class tblUser
{
public tblUser()
{
this.tblMilkMen = new HashSet<tblMilkMan>();
}
public int UserID { get; set; }
public string LogonName { get; set; }
public string Password { get; set; }
public int PasswordExpiresAfter { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
:
// more fields
:
public virtual ICollection<tblMilkMan> tblMilkMen { get; set; }
}
public class UserDTO
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Web API Controller Method:
// GET api/MilkMan/5
[ResponseType(typeof(MilkManDTO))]
public async Task<IHttpActionResult> GettblMilkMan(int id)
{
//tblMilkMan tblmilkman = await db.tblMilkMen.FindAsync(id);
MilkManDTO milkMan = await db.tblMilkMen.Select(b => new MilkManDTO()
{
RecordID = b.RecordID,
UserID = b.UserID,
IsMyTurn = b.IsMyTurn,
RoundRobinOrder = b.RoundRobinOrder,
User = //???? Error//
}).SingleOrDefaultAsync(b => b.RecordID == id);
if (milkMan == null)
{
return NotFound();
}
return Ok(milkMan);
}
You can nest a new UserDTO and use the same initialization list technique.
MilkManDTO milkMan = await db.tblMilkMen.Select(b => new MilkManDTO()
{
RecordID = b.RecordID,
UserID = b.UserID,
IsMyTurn = b.IsMyTurn,
RoundRobinOrder = b.RoundRobinOrder,
User = new UserDTO {
UserID = b.User.UserID,
FirstName = b.User.FirstName,
LastName = b.User.LastName,
}
}).SingleOrDefaultAsync(b => b.RecordID == id);
This code may throw a null reference exception on b.User.UserID if there is not associated User and thus User could be null. You would need to deal with this with either a ?? coalesce, ternary (b.User == null ? "DefaultFirstName" : b.User.FirstName) or omit the entire reference User = (b.User == null ? (UserDTO)null : new UserDTO { ... }). null's make this kind of thing fun.
With C# 6 we have null reference operator .? that makes this much more succinct.

Categories

Resources