Search code examples
c#asp.netentity-frameworkasp.net-corerazor-pages

Trying to read from database table, but output is incorrect


Here is my code:

tracks.cshtml

@page
@using Project.Models
@model Project.Pages.TracksModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<div class="row">
    <h1 class="display-2">Tracks</h1>
    <table class="table">
        <thead class="thead-inverse">
            <tr>
                <th>Artist name</th>
                <th>Album name</th>
                <th>Track ID</th>
                <th>Track name</th>
            </tr>
        </thead>

        <tbody>
            @foreach (Track track in Model.Tracks)
            {
                <tr>
                    <td>@Html.DisplayFor(modelArtists => track.Artist.Name)</td>
                    <td>@Html.DisplayFor(modelAlbums => track.Album.Title)</td>
                    <td>@track.TrackId</td>
                    <td>@track.Name</td>
                    <td><a asp-page="/Tracks/Details" asp-route-id="@track.TrackId">Details</a></td>
                    <td><a asp-page="/Tracks/Edit" asp-route-id="@track.TrackId">Edit</a></td>
                    <td><a asp-page="/Tracks/Delete" asp-route-id="@track.TrackId">Delete</a></td>
                </tr>
            }
        </tbody>
    </table>
</div>
<div class="row">
    <p>Enter a name & TrackID (over 3510) for a new track:&nbsp;</p>
    <form method="POST">
        <div><input asp-for="Track.Name" /></div>
        <div><input asp-for="Track.TrackId" /></div>
        <input type="submit" />
    </form>
</div>
<div>
    <a asp-page="/Index">Home</a>
</div>

tracks.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Project.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Project.Pages
{
    public class TracksModel : PageModel
    {
        private Chinook db;

        public TracksModel(Chinook injectedContext)
        {
            db = injectedContext;
        }
        public IEnumerable<Track> Tracks { get; set; }
        public void OnGetAsync()
        {
            ViewData["Title"] = "Chinook Web Site - Tracks";
            Tracks = db.Tracks.Include(a => a.Album)
            .Include(a => a.Artist);
        }
        [BindProperty]
        public Track Track { get; set; }

        public IActionResult OnPost()
        {
            if (ModelState.IsValid)
            {
                db.Tracks.Add(Track);
                db.SaveChanges();
                return RedirectToPage("/tracks");
            }
            return Page();
        }

        public IActionResult DeleteTrack(int TrackId)
        {
            var track = db.Tracks.Find(TrackId);

            if (track == null) return Page();

            db.Tracks.Remove(track); db.SaveChanges(); return RedirectToPage("/tracks");
        }
    }
}

GitHub repo

There is something wrong as the page appears to repeat the artist names multiple times, to such an extent that all the artists names that are displayed begin with the letter A. I should say that the way I have the database set up, ArtistId is a foreign key of the Album table, while AlbumId is a foreign key of the Tracks table.

Maybe I need clauses like .Where(a => a.AlbumId == Tracks.AlbumId) and .Where(a => a.ArtistId == Album.ArtistId)? I tried to write something like that, but I must have done it wrong as it just stopped the tracks from displaying altogether. Any help please? TIA

Edit: I'm noticing that whereas my Album titles are joined from the Album table, my Artist names are not, even though the method is essentially just the same. It occurs to me that whereas the Album table is directly connected to the Track table (through the AlbumId foreign key), the Artist table is not. It is only indirectly related to the 'Track' table, through the Album table`.

Artist----Album----Track

Edit2: I was told that Where is not needed in this instance, and that I should use 'ThenInclude`, so I've currently got

   public void OnGetAsync()
    {
        ViewData["Title"] = "Chinook Web Site - Tracks";
        Tracks = db.Tracks.Include(a => a.Album).ThenInclude(b => b.Artist).ToList();
    }

It's still resulting in the exact same error though.


Solution

  • The key point is at Context.Chinook.cs

    protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                ....
    
                modelBuilder.Entity<Track>()
                .ToTable("tracks")
                .HasOne(mtype => mtype.Artist)
                .WithMany(trk => trk.Tracks)
                .HasForeignKey(mtype => mtype.AlbumId)
                .HasForeignKey(mtype => mtype.GenreId)
                .HasForeignKey(mtype => mtype.MediaTypeId)
                .OnDelete(DeleteBehavior.NoAction);
            }
    

    HasForeignKey chains must to be separated if they are not related to above relation(1 to n, n to 1, 1 to 1). The followings are that codes I have modified.

    Context.Chinook.cs

    protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Album>()
                .ToTable("albums")
                        .HasOne(al => al.Artist)
                    .WithMany(ar => ar.Albums)
                    .HasForeignKey(al => al.ArtistId);
    
                modelBuilder.Entity<Artist>().ToTable("artists");
    
                modelBuilder.Entity<Media_type>()
                .ToTable("media_types")
                .HasKey(c => c.MediaTypeId);
    
                modelBuilder.Entity<Genre>().ToTable("genres");
    
                modelBuilder.Entity<Track>()
                .ToTable("tracks")
                .HasOne(trk => trk.Album)
                .WithMany(trk => trk.Tracks)
                .HasForeignKey(trk => trk.AlbumId)
                 .OnDelete(DeleteBehavior.NoAction);
    
                modelBuilder.Entity<Track>()
                .ToTable("tracks")
                .HasOne(trk => trk.Genre)
                .WithMany(trk => trk.Tracks)
                .HasForeignKey(trk => trk.GenreId);
    
                modelBuilder.Entity<Track>()
                .ToTable("tracks")
                .HasOne(trk => trk.Media_type)
                .WithMany(trk => trk.Tracks)
               .HasForeignKey(mtype => mtype.MediaTypeId);
            }
    

    Entities.Artist

    public class Artist
    {
         public int ArtistId { get; set; }
         public string Name { get; set; }
    
         public ICollection<Album> Albums { get; set; }
    }
    

    Entities.Genre

    public class Genre
    {
        public int GenreId { get; set; }
        public string Name { get; set; }
    
        public ICollection<Track> Tracks { get; set; }
    }
    

    Entities.Media_type

    public class Media_type
    {
        public  int MediaTypeId { get; set; }
        public  string Name { get; set; }
    
        public ICollection<Track> Tracks { get; set; }
    }
    

    Entities.Track

        public class Track
        {
            public int TrackId { get; set; }
            public string Name { get; set; }
            public int? AlbumId { get; set; }
            public int? MediaTypeId { get; set; }
            public int? GenreId { get; set; }
            public string Composer { get; set; }
            public int? Milliseconds { get; set; }
            public int? Bytes { get; set; }
            public int? UnitPrice { get; set; }
    
            public Album Album { get; set; }
            public Genre Genre { get; set; }
            public Media_type Media_type { get; set;}
        }
    

    WebApp.tracks.cshtml.cs

       public void OnGetAsync()
       {
            ViewData["Title"] = "Chinook Web Site - Tracks";
    
            Tracks = db.Tracks
            .Include(a => a.Album)
            .ThenInclude(a => a.Artist);
       }
    

    WebApp.tracks.cshtml

       <tbody>
                    @foreach (Track track in Model.Tracks)
                    {
                        <tr>
                            <td>@Html.DisplayFor(modelArtists => track.Album.Artist.Name)</td>
                            <td>@Html.DisplayFor(modelAlbums => track.Album.Title)</td>
                            <td>@track.TrackId</td>
                            <td>@track.Name</td>
                            <td><a asp-page="/Tracks/Details" asp-route-id="@track.TrackId">Details</a></td>
                            <td><a asp-page="/Tracks/Edit" asp-route-id="@track.TrackId">Edit</a></td>
                            <td><a asp-page="/Tracks/Delete" asp-route-id="@track.TrackId">Delete</a></td>
                        </tr>
                    }
    </tbody>
    

    Reference: Entity Framework Core Relationships