Reputation: 3438
I have a many-to-many relationship between Post
and Tag
, represented by the join entity PostTag
. I am currently able to access the PostTag
table in my Details
view of the Post
class; for example, I can view a particular Post
and display the associated PostTags
. However, I have only been able to display the PostTag
properties PostId
and TagId
, while I'd like to display properties related to the Tag
class via the PostTag
entity.
There are several posts online detailing ways to display many-to-many relationships, but they all seem to be broadly different to the approach I have taken, and so do not seem to be easily translatable to my project.
Models
public class Post
{
public int Id { get; set; } // primary key
public string Title { get; set; }
public string Description { get; set; }
public DateTime PublicationDate { get; set; }
public ICollection<PostTag> PostTags { get; } = new List<PostTag>();
}
public class Tag
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<PostTag> PostTags { get; } = new List<PostTag>();
}
public class PostTag
{
public int PostId { get; set; }
public Post post { get; set; }
public int TagId { get; set; }
public Tag tag { get; set; }
}
Relevant part of PostsController
public class PostsController : Controller
{
private readonly Website.Data.ApplicationDbContext _context;
public PostsController(Website.Data.ApplicationDbContext context)
{
_context = context;
}
// GET: Post/Details/5
public async Task<IActionResult> Details([Bind(Prefix = "Id")]int? id)
{
if (id == null)
{
return NotFound();
}
var post = await _context.Post
.Include(p => p.Author)
.Include(p => p.PostTags)
.FirstOrDefaultAsync(m => m.Id == id);
if (post == null)
{
return NotFound();
}
return View(post);
}
}
Db context
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Author> Author { get; set; }
public DbSet<Post> Post { get; set; }
public DbSet<Tag> Tag { get; set; }
public DbSet<PostTag> PostTag { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId });
}
}
View
@model Website.Models.Post
@foreach (var postTag in Model.PostTags)
{
<tr>
<td>
@Html.DisplayFor(modelItem => postTag)
</td>
</tr>
}
This view displays the PostTags
associated with a particular Post
. For example, at https://localhost:xxxxx/Posts/Details/1
, the Post
with Id == 1
is accessed, and its associated Tags
are displayed.
PostId
1
TagId
2
PostId
1
TagId
4
PostId
1
TagId
5
I can't seem to access Tag.Title
; for example, @Html.DisplayFor(modelItem => postTag.tag.Title)
does not display any view. My desired output would be, assuming Tag
rows of Fiction
, Nonfiction
and Classic
have Id
values equal to 2
, 4
and 5
,
Fiction
Nonfiction
Classic
Appreciate any advice here.
edit:
Upon loading the Details page in debug mode, the post
variable contains all the expected properties including PostTags
. PostTags
contains the expected number of elements (three PostTag
elements because this Post
instance has three associated Tags
). Each element contains properties of PostId
, TagId
, post
and tag
; however, tag
is null
. This is the issue, as I'm trying to access tag.Title
. Screenshot of local variables below:
Upvotes: 0
Views: 652
Reputation: 20116
You could include multiple levels of related data using the ThenInclude method.
var post= await _context.Post
.Include(p => p.Author)
.Include(p => p.PostTags)
.ThenInclude(pt => pt.Tag)
.FirstOrDefaultAsync(p => p.Id == id);
many-to-many DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(builder);
modelBuilder.Entity<PostTag>()
.HasKey(pt => new { pt.PostId, pt.TagId });
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId);
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId);
}
Upvotes: 2