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?
Related
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
I'm using ASP.NET Core 2.2. I have 2 models and a viewmodel which injects data to a view. I want to order results based on their productType. Let's make it clear
This is Product model
public class Product
{
public int ProductID { get; set; }
public int ProductName { get; set; }
public string ProductImage { get; set; }
public int ProductTypeID { get; set; }
[ForeignKey("ProductTypeID")]
public virtual ProductType ProductType{ get; set; }
}
This is ProductType model
public class ProductType
{
public int ProductTypeID { get; set; }
public int ProductTypeName { get; set; }
public string ProductTypeImage { get; set; }
public string ProductTypeDescription { get; set;
}
And finally this is DishesViewModel
public class DishesVM
{
public IEnumerable<ProductType> ProductType { get; set; }
public IEnumerable<Product> Product { get; set; }
}
In MyController I get data from DB then with automapper, map them to DishViewModel
public class HomeController : Controller
{
public IActionResult Dishes()
{
var productTypes= _context.ProductType.OrderBy(p =>p.ProductTypeID).ToList();
var products= _context.Products.OrderBy(p => p.ProductID).ToList();
var DishesVM = new DishesVM();
DishesVM.ProductType = _mapper.Map<IEnumerable<ProductType>>(productTypes);
DishesVM.Product = _mapper.Map<IEnumerable<Product>>(products);
}
}
Now in Dishes View I can have nested foreach
#model DishesViewModel
<div>
foreach(var pt in Model.ProductType)
{
<h1>pt.ProductTypeName</h1>
foreach(var p in Model.Product)
{
p.ProductName
}
}
</div>
This works fine but the only problem it has, is it returns all products. but I want each Product Category has its Products In front of its header. This is visual representation of what I want and what I have now.
This is what I want
But this is what I have
You have to filter your products by product type in each iteration. At the moment you just display all products for each product type:
<div>
foreach(var type in Model.ProductType)
{
//products which belong to the particular type
var productsForType = Model.Product.Where(x => x.ProductTypeID == type.ProductTypeID);
<h1>pt.ProductTypeName</h1>
foreach(var product in productsForType)
{
product.ProductName
}
}
</div>
This will give you a jump on the linq statement:
var list = productTypes.Where(x => x.ProductTypeID == 1).Select(x => new Product()
{
ProductImage = x.ProductTypeImage,
}).ToList();
You will need to decide on what to put into the where clause, I'm using ProductTypeID
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));
});
I am very new to .NET MVC and trying to learn MVC. I know that I am doing total wrong here, so I need your help. What I try to do is listing a set of 10 companies, then for each of those company listing the contacts based on the companyID. Please assume that the Entitites and DbContext are set properly, just the problem is between Controller and View is where I couldn't figure out how to:
Here is my Model:
namespace ERP.Models
{
[Table("ERP_Company")]
public class ERP_Company
{
[Key]
public int CompanyID { get; set; }
public string Name { get; set; }
}
[Table("ERP_CompanyContact")]
public class ERP_Contact
{
[Key]
public int ContactID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int CompanyID { get; set; }
}
}
The methods for getting Company and Contact list from the database:
namespace ERP.Models
{
public class Method1
{
private ERPEntities db = new ERPEntities();
public List<ERP_Company> getCompanyList()
{
List<ERP_Company> companyList = (
from c in db.ERP_Company
where c.Name.Contains("Network")
select c).Take(10).ToList();
return companyList;
}
public List<ERP_Contact> getContactList(int CompanyID)
{
List<ERP_Contact> contactList = (
from cc in db.ERP_CompanyContact
where cc.CompanyID == CompanyID
select cc).Take(50).ToList();
return contactList;
}
}
}
Here is my controller where I am doing wrong:
namespace ERP.Controllers
{
public class Test1Controller : Controller
{
//private ERPEntities db = new ERPEntities();
Method1 _repository = new Method1();
public ActionResult Index()
{
ViewData["Company"] = _repository.getCompanyList();
ViewData["Contact"] = _repository.getContactList(CompanyID); // <-- Incorrect Here, but just to show that I want to pass the CompanyID
return View();
}
}
}
Lastly, the View which I want to list the Company, then query all contacts based on CompanyID and list them.
<ul>
#foreach (var item in ViewData["Company"] as List <ERP.Models.ERP_Company>
)
{
<li>#item.CompanyID | #item.Name</li>
<!-- Here is an EXAMPLE that I want to QUERY the Contact recordset and list all the contacts based on the CompanyID -->
<ul>
#for (var i = 0; i < 5; i++)
{<li>Contact #i</li>}
</ul>
}
</ul>
Is it possible to loop through the Contact model (or recordset) within a loop? How can I accomplish this?
Thanks in advance,
Here is how I would implement your case, not in a best way but in a simple way.
Entities:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Contact> Contacts { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int CompanyId { get; set; }
}
Service:
public class CompanyService
{
public List<Company> getCompanyList()
{
using (ERPEntities db = new ERPEntities())
{
return db.Companies
.Include("Contacts")
.Where(e => e.Name.Contains("Network"))
.Take(10)
.ToList();
}
}
}
Controller:
public HomeController(CompanyService companyService)
{
this.companyService = companyService;
}
public ActionResult Index()
{
List<Company> companies = this.companyService.getCompanyList();
return View(companies);
}
View:
<ul>
#foreach (var company in Model)
{
<li>#company.Id | #company.Name</li>
if (company.Contacts.Count > 0)
{
<ul>
#foreach (var contact in company.Contacts)
{
<li>#contact.FirstName</li>
}
</ul>
}
}
</ul>
In other hand, judging from your implementation, I feel like you may need to work more on fundamental skills like data structure, C#/OOP fundamental then ASP.NET MVC in respective order.
I used scaffolding to create the Index, Details, Create, Edit and Delete views and the controller. I have two view models (Parent / Child) relation. In my Index view I want to display the list of Teams as well as some information on the players (Parent / child). For example I want to display in the Index view the teams with the players count per team and last players that was modified. I am not sure where to begin.
Example:
(Team) Red -- (Last Modified) 01/02/2015 -- (Number Players) 10 and so on.
Team ViewModel
public class TeamVM
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime? LastActivity { get; set; }
public string NumberPlayers { get; set; }
public IList<PLayerVM> PlayerVM { get; set; }
}
Player ViewModel
public class PlayerVM
{
public int ID { get; set; }
public int TeamID { get; set; }
public string PlayerInfo { get; set; }
public DateTime? CreateDate { get; set; }
}
Other ViewModel
public class TeamViewModel
{
public List<Team> Teams{ get; set; }
}
Controller
public ActionResult Index()
{
TeamViewModelviewModel = new TeamViewModel();
viewModel.Teams= db.Teams.ToList();
return View(viewModel);
}
db.Products.ToList()?? I assume that is where you mean db.Teams.ToList()?
You are using viewmodels, so you should map the db data to your viewmodels first:
public ActionResult Index()
{
var teams = db
.Teams
.Include("Players") // Assuming your Team entity has a collection of Players
.SelectMany(t => new TeamVM {
ID = t.ID,
// etc..
})
.ToList();
return View(new TeamViewModel { Teams = teams });
}
model:
public class TeamVM
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime? LastActivity { get; set; }
public IList<PLayerVM> PlayerVM { get; set; }
public int NumberPlayers {
get { return PlayerVM.Count(); }
}
}
Then in your view:
#model MyProject.Models.TeamViewModel
<table>
#foreach(var team in Model.Teams.ToList()) {
<tr>
<td>#team.Name</td> // Name
<td>#team.NumberPlayers</td> // Playercount
<td>#team.PlayerVM.Max(p => p.LastActivity).LastActivity</td> // Last edited
</tr>
}
</table>