Many-to-many relationship and multi select in asp.net core - entity-framework

I'm trying to learn asp.net core with razor and I'm trying to make a videogame database to keep a track of finished games, games I haven't played yet, etc.
But I have a problem. I have a table Game and a table Developer. Since a game can have many developers and a developer can have many games y made a third table DeveloperXGame.
They are something like this
public class Game
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Developer
{
public int Id { get; set; }
public string Name { get; set; }
}
public class DeveloperXGame
{
public int DeveloperId { get; set; }
public int JuegoId { get; set; }
public Developer Developer { get; set; }
public Game Game { get; set; }
}
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Game> Game { get; set; }
public DbSet<Developer> Developer { get; set; }
public DbSet<DeveloperXGame> DeveloperXGame { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DeveloperXGame>()
.HasKey(m => new { m.DeveloperId, m.GameId });
}
}
I already did the pages for the developers so I first create them manually. Now I'm trying to create the games and I want to show a select where I can select one or more developers of that list (next step will be to try to add them with ajax through the games page if they don't exists). But I'm lost beyond this point.
I don't know how to load the list of developers in that list and later on post how to save those selected items in the table DeveloperXGame.
Thanks

You can remove public DbSet<DeveloperXGame> DeveloperXGame { get; set; } from your context.
Index.cshtml.cs
public class IndexModel : PageModel
{
private readonly ApplicationDbContext _context;
public IndexModel(ApplicationDbContext context)
{
_context = context;
}
public Game Game { get; set; }
public IEnumerable<int> Developers { get; set; }
public IEnumerable<SelectListItem> DeveloperList { get; set; }
public IActionResult OnGet()
{
var developers = from m in _context.Developers
select m;
DeveloperList = developers.Select(m => new SelectListItem { Value = m.Id.ToString(), Text = m.Name });
return Page();
}
}
Here is the view Index.cshtml
#page
#model RazorPages.TestGame.Pages.Games.IndexModel
#{
ViewData["Title"] = "Index";
}
<div class="row">
<div class="col-md-4">
<form method="post">
<div class="form-group">
<label asp-for="Game.Name" class="control-label"></label>
<input asp-for="Game.Name" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Developers" class="control-label"></label>
<select asp-for="Developers" asp-items="Model.DeveloperList">
<option value="">All</option>
</select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
You can also use ViewData to pass your developers list in your view. You can go through this sample web as in official doc here.Hope it helps you to get started!

Related

Can't access to foreign key properties using Entity Framework in Razor Pages project

I'm trying to display foreign key properties values using class that should have access to it:
#foreach (var item in Model.UserIssues)
{
<div class="card scroll" style="width:22rem;">
<div class="card-header p-4">
<div class="d-flex justify-content-between">
<div>
<p class="card-label mb-0"></p>
<h5>#Html.DisplayFor(i => item.Case.CaseNumber)</h5>
</div>
<div>
<a class="btn button-idle-add" data-bs-toggle="tooltip" data-bs-title="Dodaj Pracownię" asp-route-id="#item.Id" asp-page="/Cases/AddLaboratory"><i class="fa-solid fa-plus"></i></a>
</div>
</div>
<div class="d-flex justify-content-between mt-3">
<div>
<p class="card-input">#Html.DisplayFor(e => item.Case.Principal)</p>
</div>
<div>
<p class="card-input">#Html.DisplayFor(e => item.Case.Date)</p>
</div>
</div>
</div>
<div class="card-body">
<div class="m-1 p-3 card-lab">
<div class="d-flex justify-content-between align-items-center">
<p>#Html.DisplayFor(e => item.IssueNumber)</p>
<p class="pe-3 ps-3" style="border-radius:15px; background-color: palegreen;"></p>
</div>
<p>#Html.DisplayFor(e => item.Specialist.Laboratory)</p>
<p>#Html.DisplayFor(e => item.Specialist.FullName)</p>
</div>
</div>
</div>
}
public class IndexModel : PageModel
{
private readonly IRepository<Issue> issueRepository;
private readonly IRepository<Specialist> specialistRepository;
public IndexModel(IRepository<Issue> issueRepository, IRepository<Specialist> specialistRepository)
{
this.issueRepository = issueRepository;
this.specialistRepository = specialistRepository;
}
public List<Issue> AllIssues { get; set; }
public async Task<IActionResult> OnGetAsync()
{
var loggedUser = specialistRepository.GetAll().FirstOrDefault(u => u.Login == User.Identity.Name);
UserIssues = issueRepository.GetAll().Where(i => i.Specialist.Id == loggedUser.Id).ToList();
return Page();
}
}
So for Specialist it works just fine:
#Html.DisplayFor(e => item.Specialist.FullName)
But for the Case it doesn't work, nothing is displayed:
#Html.DisplayFor(i => item.Case.CaseNumber)
Here are my models for Entity Framework setup:
public class Case
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "Uzupełnij pole")]
public string CaseNumber { get; set; }
[Required(ErrorMessage = "Uzupełnij pole")]
public string Principal { get; set; }
[Required(ErrorMessage = "Uzupełnij pole")]
public string Description { get; set; }
[Required(ErrorMessage = "Uzupełnij pole")]
public string Date { get; set; }
public ICollection<Issue> Issues { get; set; }
}
public class Issue
{
[Key]
public int Id { get; set; }
public string Comment { get; set; }
[Required(ErrorMessage = "Uzupełnij pole")]
public string IssueNumber { get; set; }
public Case Case { get; set; }
public Specialist Specialist { get; set; }
}
public class Specialist : IdentityUser
{
[Required]
public string Login { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Laboratory { get; set; }
public string FullName
{
get
{
return FirstName + " " + LastName;
}
}
public ICollection<Issue> Issues { get; set; }
}
Is my Entity Framework setup wrong? How can I display property values for the Case entity?
Using Entity Framework in case you want to get a specialist with id e775704b-5298-4173-82c8-c15d884e0695 and include the issues for this specialist you can use the include method in order to get the associated data.
Specialist? specialist = context.Specialists.Where(m => m.Id == "e775704b-5298-4173-82c8-c15d884e0695").Include(m => m.Issues).FirstOrDefault();
if (specialist != null)
{
foreach (var issue in specialist.Issues)
{
string issueNumber = issue.IssueNumber;
}
}
You can do the same thing for the entire list like this.
List<Specialist> specialists = context.Specialists.Include(m => m.Issues).ToList();
foreach (var specialist in specialists)
{
foreach (var issue in specialist.Issues)
{
string issueNumber = issue.IssueNumber;
}
}

How do I load data that has a Many to Many relationship in ASP.NET Core?

I'm working on a many to many relationship in my ASP.NET Core application. From reading sources online I understand that this isn't officially supported yet and so an intermediary class is needed to make it all work.
The problem that I have is that once I have created my 'many to many' relationship, I don't know how best to display the data in my view and I'm struggling to traverse everything with this particular setup.
In my example there are two tables Part and Supplier, one Part can have many Supplier just as one Supplier can have many Part.
The first thing I did was create my two entity classes Part and Supplier
Part.cs
public class Part
{
public int PartId { get; set; }
public string PartName { get; set; }
public string PartNumber { get; set; }
public string ShortDescription { get; set; }
public string LongDescription { get; set; }
public string Category { get; set; }
//Intermediate entity
public IList<PartSupplier> PartSupplier { get; set; }
}
Supplier.cs
public class Supplier
{
public int SupplierId { get; set; }
public string SupplierName { get; set; }
public string Website { get; set; }
public int? Telephone { get; set; }
public string Email { get; set; }
//Intermediate entity
public IList<PartSupplier> PartSupplier { get; set; }
}
You'll see from the above code that I've placed an intermediate table that creates this many to many relationship called PartSupplier
PartSupplier.cs
public class PartSupplier
{
public int PartId { get; set; }
public Part Part { get; set; }
public int SupplierId { get; set; }
public Supplier Supplier { get; set; }
}
At this point, I move to my data context and override the model building portion of the code to utilise the Fluent API.
Context.cs
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<PartSupplier>().HasKey(ps => new { ps.PartId, ps.SupplierId });
base.OnModelCreating(builder);
}
public DbSet<Part> Parts { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<PartSupplier> PartSuppliers { get; set; }
Ok, so far so good. Now I need to display this information in a view, in this view specifically, I'd like to show all the parts and next to them all the available suppliers that part can be bought from.
I load the data in the following manner although, this is where I'm a little unsure how I should be structuring the query. Should I call the intermediary table or call Part and then include Supplier? I'm not sure
PartController.cs
public IActionResult Index()
{
var data = _context.Part.Include(p => p.Supplier);
return View(data);
}
Index.cshtml
#model IEnumerable<Part>
<div class="container">
<div class="row">
<div class="col">
<h2>Parts</h2>
<a asp-action="Create" asp-controller="Parts">Add Part</a>
<table class="table">
#foreach (var part in Model)
{
<tr>
<td>
<a asp-action="Edit" asp-route-id="#part.PartId" asp-controller="Part">#part.PartName</a>
</td>
<td>
#part.PartNumber
</td>
<td>
#part.ShortDescription
</td>
<td>
...I'd like to show the suppliers here...
</td>
</tr>
}
</table>
</div>
</div>
</div>
I think I've almost got it but really would like some assistance getting over this final hurdle. Many thanks
A cascade relationship is missing in Index.cshtml, you need to add ThenInclude.
public IActionResult Index()
{
var data = _context.Parts.Include(x => x.PartSupplier)
.ThenInclude(y=>y.Supplier);
return View(data);
}
Then in Index.cshtml.
<table class="table">
#foreach (var part in Model)
{
<tr>
<td>
<a asp-action="Edit" asp-route-id="#part.PartId" asp-controller="Part">#part.PartName</a>
</td>
<td>
#part.PartNumber
</td>
<td>
#part.ShortDescription
</td>
<td>
<ul>
#* Here has been changed. *#
#foreach(var supplier in part.PartSupplier)
{
<li>#supplier.Supplier.SupplierName #supplier.Supplier.Website</li>
}
</ul>
</td>
</tr>
}
</table>
In database, I insert some data, such as Part and PartSupplier.
Result.

selectList razor tag helpers asp.netCore

I want to make a drop down list of "Trailers" and "Customers" available in my "Order" form. I am able to use the Html tag helper to pass Trailer data from database to the view in the "Order" form but i am not able to do the same for Customers using the razor select tag helper. Why isn't the razor select tag helper not passing values from the database to the view? Below are snippets of my code. I am confused as to why it's not working
Trailer Class
public class Trailer
{
public string SerialNumber { get; set; }
public string TrailerNumber { get; set; }
public string TrailerStatus { get; set; }
public int TrailerID { get; set; }
public virtual Order OrderforTrailer { get; set; }
public Trailer()
{
TrailerStatus = "Available";
}
}
Customer class
public class Customer
{
public string CustomerName { get; set; }
public string StreetNumber { get; set; }
public string StreetName { get; set; }
public string ZipCode { get; set; }
public string State { get; set; }
public int CustomerID { get; set; }
public IList<Order> CustomerOrders { get; set; }
}
Order Class
public class Order
{
public string OrderNumber { get; set; }
public string OrderStatus { get; set; }
public int OrderID { get; set; }
public int TrailerForLoadID { get; set; }
public virtual Trailer TrailerForLoad { get; set; }
public int CustomerOrdersID { get; set;}
public virtual Customer CustomerOrders { get; set; }
public Order()
{
OrderStatus = "Available";
}
}
AddOrderViewModel
public string OrderNumber { get; set; }
public int TrailerID { get; set; }
public List<SelectListItem> TrailersForLoad { get; set; }
public int CustomerID { get; set; }
public List<SelectListItem> CustomersOrder { get; set; }
public AddOrderViewModel()
{
}
public AddOrderViewModel(IEnumerable<Trailer> trailersForLoad, IEnumerable<Customer> customersOrder)
{
TrailersForLoad = new List<SelectListItem>();
foreach (var trailer in trailersForLoad)
{
TrailersForLoad.Add(new SelectListItem
{
Value = (trailer.TrailerID).ToString(),
Text = trailer.TrailerNumber
});
};
CustomersOrder = new List<SelectListItem>();
foreach (var customer in customersOrder)
{
CustomersOrder.Add(new SelectListItem
{
Value = (customer.CustomerID).ToString(),
Text = customer.CustomerName
});
};
}
}
Order controller
public IActionResult Add()
{
IList<Trailer> trailerForLoad = context.Trailers.Where
(c => c.TrailerStatus == "Available").ToList();
IList<Customer> customerOrder = context.Customers.ToList();
AddOrderViewModel addOrderViewModel =
new AddOrderViewModel(trailerForLoad, customerOrder);
return View(addOrderViewModel);
}
[HttpPost]
public IActionResult Add(AddOrderViewModel addOrderViewModel)
{
if (ModelState.IsValid)
{
Order newOrder = new Order()
{
OrderNumber = addOrderViewModel.OrderNumber,
TrailerForLoad = context.Trailers.
Where(x => x.TrailerID == addOrderViewModel
.TrailerID).Single(),
CustomerOrders = context.Customers
.Single(x => x.CustomerID==addOrderViewModel.CustomerID)
};
context.Orders.Add(newOrder);
trailerSelected = context.Trailers.Where(x =>
x.TrailerID == addOrderViewModel.TrailerID).Single();
trailerSelected.TrailerStatus = "Unavailable";
context.SaveChanges();
return Redirect("/Order");
}
return View(addOrderViewModel);
}
The form in the view should display a list of customers
<form asp-controller="Order" asp-action="Add" method="post">
<fieldset>
<div class="form-group">
<label asp-for="OrderNumber">Order number </label>
<input class="form-control" asp-for="OrderNumber" />
<span asp-validation-for="OrderNumber"></span>
</div>
<div class="form-group">
<label asp-for="TrailersForLoad">Trailer</label>
#Html.DropDownListFor(x => x.TrailerID, Model.TrailersForLoad)
<span asp-validation-for="TrailersForLoad"></span>
</div>
<div class="form-group">
<label asp-for="CustomerID">Customers Name</label>
<select asp-for="CustomerID"
asp-items="Model.CustomersOrder"></select>
<span asp-validation-for="CustomerID"></span>
</div>
<div>
<input type="submit" value="Submit" name="submitButton" />
</div>
</fieldset>
You are using the SELECT tag helper incorrectly. In your current code, you are using a self closing tag approach! Instead you should use an explicit </SELECT> closing tag
This should work
<select asp-for="CustomerID" asp-items="Model.CustomersOrder"></select>

MVC4 Entity Framework 5 Many-To-Many Save Entity to Database

Hi I've been stuck to long on this problem. I've looked at a lot of examples but i cant find what im looking for. Any help is appreciated.
I use the Code-First approach.
I've enabled migration and the database is fine [Student] - [StudentCourse] - [Course].
Scenario: I have two entites -> Student
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Course> Courses { get; set; }
}
And Course
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual List<Student> Students { get; set; }
}
Nothing fancy about that... Ive created a ViewModel ->
public class StudentCourseViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<Course> Courses { get; set; }
}
View:
#model Project.Web.Models.StudentCourseViewModel #{
ViewBag.Title = "Edit"; }
Edit
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Student</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Courses)
</div>
<div class="editor-field">
#for (int i = 0; i < Model.Students.Count(); i++)
{
<div style="border: dotted 1px; padding: 5px; margin: 10px;">
#Html.HiddenFor(s => s.Students[i].Id)
#Html.LabelFor(s => s.Students[i].Name[i + 1])
#Html.EditorFor(s => s.Students[i].Name)
</div>
}
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset> }
Controller Action:
[HttpPost]
public ActionResult Edit(CourseStudentViewModel model)
{
var course = db.Courses.Find(model.CourseId);
course.Name = model.CourseName;
course.Description = model.CourseDescription;
course.Students = model.Students;
if (ModelState.IsValid)
{
db.Entry(course).State = System.Data.EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
(Maby this is were i go wrong...)
Anyway, I want to create a new Student with optional many courses (textboxes -> courseName)
How should i do this?
The main issue is that i always get null values (student is fine, List of courses = NULL) back from my view [httpPost]Create -action.
I'm in need of guidance how to make this approach possible.
Thx J!
Your entities are not setup correctly for a many-to-many relationship. You need another entity to handle the many-to-many mapping. It would look something like this.
public class StudentsToCourses
{
public int StudentId {get; set;}
public int CourseId {get; set;}
public virtual Student Student {get; set;}
public virtual Course Course {get; set;}
}
Then your student model should be changed to this.
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<StudentsToCourses> Courses { get; set; }
}
And your coursed model changed to this.
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual List<StudentsToCourses> Students { get; set; }
}
You also need to setup the foreign key relationship using the Fluent API. It would look something like this.
public class StudentsToCoursesConfiguration : EntityTypeConfiguration<StudentsToCourses>
{
internal StudentsToCoursesConfiguration ()
{
this.HasKey(p => new {p.StudentId, p.CourseId});
this.HasRequired(p => p.Student)
.WithMany(p => p.Courses)
.HasForeignKey(p => p.StudentId);
this.HasRequired(p => p.Course)
.WithMany(r => r.Students)
.HasForeignKey(p => p.CourseId);
}
}

MVC3 drop down list confusion

I'm using MVC3 with EF 4.1 and trying to edit a model which has a drop down list which is the reference to a parent object. Here are the models:
public class Section
{
public Guid SectionId { get; set; }
public string Title { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
public class Article
{
public Guid ArticleId { get; set; }
public DateTime? DatePosted { get; set; }
public string Title { get; set; }
public string ArticleBody { get; set; }
public Section Section { get; set; }
}
Here's the controller action to render the GET part of the edit:
public ActionResult Edit(Guid id)
{
Article article = db.Articles.Find(id);
var sections = db.Sections.ToList();
var secIndex = sections.IndexOf(article.Section);
ViewBag.SectionId = new SelectList(sections, "SectionId", "Title", secIndex);
return View(article);
}
And the View
#model CollstreamWebsite.Models.Article
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Article</legend>
#Html.HiddenFor(model => model.ArticleId)
<div class="editor-label">
#Html.LabelFor(model => model.DatePosted)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DatePosted)
#Html.ValidationMessageFor(model => model.DatePosted)
</div>
...
<div class="editor-label">
#Html.LabelFor(model => model.Section)
</div>
<div class="editor-field">
#Html.DropDownList("SectionId")
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
And finally the POST action for the edit
[HttpPost]
public ActionResult Edit(Article article)
{
if (ModelState.IsValid)
{
db.Entry(article).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(article);
}
The problem I have is that when the HttpPost Edit comes back, article.Section is null. How do I force the View to bind the Section to the article being edited.
Any help appreciated.
Don't push your Model straight to your View. Use ViewModel instead.
Something like this:
ViewModel
public class EditArticleViewModel
{
///All the properties for your Article
///The SelectListItems for your Sections
public List<SelectListItem> Sections{ get; set; }
public String SelectedSection{ get; set; }
}
Edit Get
[HttpGet]
public ActionResult Edit(Guid id)
{
EditArticleViewModel oEditArticleViewModel = new EditArticleViewModel();
//Fill in the SelectLists
List<SelectListItem> Sections= new List<SelectListItem>();
Sections.Add(new SelectListItem() { Text = "TheSelectedSection", Value = SectionId.ToString(), Selected = true});
foreach(Section otherSection in AllPossibleSections)
{
Sections.Add(new SelectListItem() { Text = otherSection.Title, Value = otherSection.Id, Selected = false});
}
oEditArticleViewModel.Sections = Sections;
return View(oEditArticleViewModel );
}
Your View
#Html.DropDownListFor(model => model.SelectedSection, Model.Sections)
//All other needed properties with their textboxes etc.
Edit Post
[HttpPost]
public ActionResult Register(EditArticleViewModel oPostedViewModel)
{
if (ModelState.IsValid)
{
//Get the Article and fill in the new properties etc.
//You can get the selectedSection from the SelectedSection Property, just cast it to a Guid.
RedirectToAction("Index", "Home");
}
//Something went wrong, redisplay the form for correction.
//Make sure to fill in the SelectListItems again.
return View(oPostedViewModel);
}
Hope it helps