Search code examples
asp.net-mvcdatabaseentity-frameworkmany-to-many

Implementing Many-To-Many relationship in Asp.Net (confused?)


My current aim is to build a database structure using classes in Entity Framework & ASP MVC.

I currently have a Users table and a Posts table. What I would like to do is create a many to many relationship for Users who have liked Posts (whilst conserving who created the post). And be able to access for each user all of the posts they have liked. Currently I have these classes but I'm unsure of how to link them as all of the online examples are linking Primary Keys from different databases where I just want to use the Username Parameter. Any help would be great. I have this so far.

 public class Posts
    {
        [Key]
        public virtual int PostId { get; set; }
        public virtual string Title { get; set; }
        public virtual string URL { get; set; }
        [DisplayName("Main Text")]
        public virtual string TextBody { get; set; }
        public int PostLikes { get; set; }
        private DateTime Datedata = DateTime.Now;
        public DateTime PostDate { get { return Datedata; } set { Datedata = value; } }
        public virtual Users User { get; set; }
        public ICollection<PostLikes> UsersWhoHaveSigned { get; set; }
    }
{
    public class Users
    {
        [Key]
        public virtual int UserId { get; set; }
        public virtual string Username { get; set; }
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }
        public virtual List<Posts> Post { get; set; }

    }
}

I have not built the UsersWhoHaveSigned table yet. Early experimentation caused me so much backtracing it was painful. Any help would be great.

Edit: I was hoping to ask for help and then appropriate that informtaion to fit my example which utilises the individual accounts add-on. This produces some addition files that are now causing interference with the code you've provided. Here is the IdentityModels.cs file.

using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;

namespace Coursework2.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit https://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }

        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    }
}

I believe that the assembly directives at the top are preventing system.data.entity from being used so when I try to implement ApplicationDbContext : DbContext I get error messages :/

Ideally I'm looking to use the IdentityModels.cs file as a replacement for the users class. But still very lost.


Solution

  • First of all, I recommend that you use the singular form for your class names, as EF will automatically pluralize table names.

    Second, for a key property, you can just use the term Id, without any annotations, and EF will pick it up as the principal key.

    Finally, I'll assume you are looking to use a Code-First approach. Consider the following classes (yours, but refactored for clarity purpose):

    public class Post
    {
    
        public virtual Guid Id { get; set; }
    
        public virtual string UserName { get; set; }
    
        public virtual User User { get; set; }
    
        public virtual ICollection<PostLike> Likes { get; set; }
    
    }
    
    public class PostLike
    {
    
        public virtual Guid Id { get; set; }
    
        public virtual Guid PostId { get; set; }
    
        public virtual Post Post { get; set; }
    
        public virtual string UserName { get; set; }
    
        public virtual User User { get; set; }
    
    }
    
    public class User
    {
    
        public virtual Guid Id { get; set; }
    
        public virtual string UserName { get; set; }
    
        public virtual ICollection<Post> Posts { get; set; }
    
        public virtual ICollection<PostLike> Likes { get; set; }
    
    }
    

    To make it work, you'd need a DbContext such as the following. Pay attention to the OnModelCreating method, which is where the magic happens:

    public class ApplicationDbContext
        : DbContext
    {
    
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) 
            : base(options)
        {
    
        }
    
        public DbSet<Post> Posts { get; set; }
    
        public DbSet<PostLike> PostLikes { get; set; }
    
        public DbSet<User> Users { get; set; }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<User>()
                .HasAlternateKey(u => u.UserName);
            modelBuilder.Entity<User>()
                .HasMany(u => u.Posts)
                .WithOne(p => p.User);
            modelBuilder.Entity<Post>()
                .HasOne(p => p.User)
                .WithMany(u => u.Posts)
                .HasForeignKey(p => p.UserName)
                .HasPrincipalKey(u => u.UserName);
            modelBuilder.Entity<Post>()
                .HasMany(p => p.Likes)
                .WithOne(pl => pl.Post);
            modelBuilder.Entity<PostLike>()
                .HasOne(pl => pl.Post)
                .WithMany(p => p.Likes);
            modelBuilder.Entity<PostLike>()
                .HasOne(pl => pl.User)
                .WithMany(u => u.Likes)
                .HasForeignKey(pl => pl.UserName)
                .HasPrincipalKey(u => u.UserName);
        }
    
    }
    

    Voila! I hope it answers your question ;) If so, please don't forget to mark my post as the answer!

    Edit:

    I'll provide some explanations, that I had left out to answer your question ASAP.

    So, first thing you need to do, is to declare the UserName as an alternate key, because you want to create relationships depending on it, and you already have the 'Id' principal key declared.

    Second, on each object that should own a User reference base on the UserName alternate key, you need to declare the object's UserName property as the foreign key of the relationship, and the User's UserName property as the principal key.

    In other words, the foreign key is the property that a referencing object uses for the relationship, and the principal key is the property based on which the referenced object is bound to the referencing one.

    Note that principal keys must have a key or alternate key constraint, or it won't work.