Search code examples
c#entity-framework-coremany-to-many.net-7.0

Can't Insert Data Into Many to Many Relationship With Entity Framework C# .Net7


I know this has probably been asked hundreds, if not thousands of times. I have been working on this for 2 days and I have looked at several answers and tried following several examples. I have been getting more and more confused.

I have a forum app with a Forums table and a Tags table. Each forum can have many tags. Each tag can be assigned to many forums. Each tag in the tags table is unique.

public class Forum : BaseModel
{        
    public string? Title { get; set; }
    public List<Post> Posts { get; set; } = new List<Post>();
    public ICollection<ForumTag> ForumTags { get; set; } = new List<ForumTag>();
}

public class Tag : BaseModel
{
    public string? Name { get; set; }
    public ICollection<ForumTag> ForumTags { get; set; } = new List<ForumTag>()
}

public class ForumTag
{
    public int ForumId { get; set; }
    public int TagId { get; set; }

    [NotMapped]
    public Forum forum { get; set; } = new();
    [NotMapped]
    public Tag tag { get; set; } = new();
}

modelBuilder.Entity<ForumTag>()
    .HasKey(t => new { t.ForumId, t.TagId });

modelBuilder.Entity<ForumTag>()
    .HasOne(pt => pt.forum)
    .WithMany(p => p.ForumTags)
    .HasForeignKey(pt => pt.ForumId);

modelBuilder.Entity<ForumTag>()
    .HasOne(pt => pt.tag)
    .WithMany(t => t.ForumTags)
    .HasForeignKey(pt => pt.TagId);

CreateMap<Forum, ForumViewModel>();

CreateMap<ForumViewModel, Forum>()
    .ForMember(q => q.CreatedBy, options => options.Ignore());
    //.ForMember(q => q.ForumTags, options => options.Ignore());

CreateMap<Post, PostViewModel>();
CreateMap<PostViewModel, Post>()
    .ForMember(x => x.CreatedBy, options => options.Ignore());

CreateMap<Tag, TagViewModel>();
CreateMap<TagViewModel, Tag>()
    .ForMember(x => x.Id, options => options.Ignore());
    //.ForMember(x => x.ForumTags, options => options.Ignore());

//CreateMap<ForumTagViewModel, ForumTag>()
//    .ForMember(q => q.forum, options => options.Ignore())
//    .ForMember(q => q.tag, options => options.Ignore());


public async Task<ActionResult<ForumViewModel>> PostForum(ForumViewModel forumViewModel)
{
    Forum? forum = null;

    try
    {                
        forum = _mapper.Map<Forum>(forumViewModel);
        _context.Forums.Add(forum);
        await _context.SaveChangesAsync();

        List<ForumTag> tags = new();                

        foreach(TagViewModel tag in forumViewModel.SelecteTags)
        {
            tags.Add(new()
            {
                ForumId = forum.Id,
                TagId = tag.Id
            });
        }
        _context.ForumTags.AddRange(tags);
        _context.SaveChanges();
    }
    catch (Exception ex)
    {

        return Problem($"Unable to post new forum.\n\n{ex}");
    }

    forumViewModel.Id = forum.Id;

    return CreatedAtAction("GetForumViewModel", new { id = forumViewModel.Id }, forumViewModel);
}

So currently after

forum = _mapper.Map<Forum>(forumViewModel);
_context.Forums.Add(forum);
await _context.SaveChangesAsync();

The forums table and Tag table are correctly populated, as well as the Post table.

After

 _context.ForumTags.AddRange(tags);
 _context.SaveChanges();

A new null forum record is created and a new null tag record is created. The ForumTag table has the id's from the null forum and tag record.

What I need to do is insert a new forum record, a new post record, and the ForumTag Id's. I'm not having any issues with the Post records.

Can someone please point me in the right direction?


Solution

  • Well, as happens from time to time, I figured it out after I created a post here. I found this blog Updating many-to-many relationships in EF Core 5 and above

    public class Forum : BaseModel
    {        
        public string? Title { get; set; }
        public List<Post> Posts { get; set; } = new List<Post>();
        public List<Tag>? Tags { get; set; }      
    }
    
    public class Tag : BaseModel
    {
        public string? Name { get; set; }
        public ICollection<Forum> Forums { get; set; }
    }
    
    public class ForumTag
    {
        public int ForumId { get; set; }
        public int TagId { get; set; }        
        public Forum forum { get; private set; }
        public Tag tag { get; private set; }
    }
    
    modelBuilder.Entity<Forum>().HasMany(x => x.Tags)
            .WithMany(x => x.Forums)
            .UsingEntity<ForumTag>(
                x => x.HasOne(x => x.tag)
                .WithMany().HasForeignKey(x => x.TagId),
                x => x.HasOne(x => x.forum)
               .WithMany().HasForeignKey(x => x.ForumId));
    
    public async Task<ActionResult<ForumViewModel>> PostForum(ForumViewModel forumViewModel)
    {
        Forum? forum = null;
    
        try
        {
            var viewModelTags = forumViewModel.SelecteTags;
    
            List<Tag> tags = new List<Tag>();
    
            foreach (var item in viewModelTags)
            {
                Tag tag = await _context.Tags.FirstOrDefaultAsync(x => x.Name == item.Name);
                tags.Add(tag);
            }
    
            forum = _mapper.Map<Forum>(forumViewModel);
            forum.Tags = new();
            forum.Tags.AddRange(tags);
            
            _context.Add(forum);
            _context.SaveChanges();
        }
        catch (Exception ex)
        {
    
            return Problem($"Unable to post new forum.\n\n{ex}");
        }
    
        forumViewModel.Id = forum.Id;
    
        return CreatedAtAction("GetForumViewModel", new { id = forumViewModel.Id }, forumViewModel);
    }