Search code examples
c#.netasp.net-coreasp.net-core-mvcasp.net-identity

How to create an Add Friend functionality between two individual user accounts in the same table in asp.net core identity


I am developing a mini social media web app and I use ASP.NET Identity to create and manage user accounts. I want to add another user account as a friend to my account. I could succesfully do that but the problem is when I checked my added friend's account, there is no update in his friends list. It's empty.

Here is my User class inherited from IdentityUser,

public class AppUser : IdentityUser
{
    public AppUser()
    {
        this.Friends = new HashSet<AppUser>();
    }
    public int Age { get; set; }
    public string Sex { get; set; }
    public string City { get; set; }
    public string Education { get; set; }
    public string Description { get; set; }
    public string? FriendOfUserId { get; set; }
    public virtual AppUser FriendOf { get; set; }

    public ICollection<AppUser> Friends { get; set; }
}

My DbContext class,

public class ApplicationDbContext : IdentityDbContext<AppUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
builder.Entity<AppUser>(x =>
        {
            x
                .HasMany(x => x.Friends)
                .WithOne(x => x.FriendOf)
                .HasForeignKey(x => x.FriendOfUserId);
        });
    }
    public DbSet<AppUser> Users { get; set; }
}

My Controller Class method to add friend,

public async Task<IActionResult> AddFriend(string id)
    {
        var addFriend = await context.Users.Include(u => u.Friends).FirstOrDefaultAsync(u => u.Id == id);
        var user = await userManager.GetUserAsync(this.User);
        var u = await context.Users.Include(u => u.Friends).FirstOrDefaultAsync(u => u.Id == user.Id);
        user.FriendOf = addFriend;
        user.Friends.Add(addFriend);
        await context.SaveChangesAsync();
        return Redirect("/");
    }

Solution

  • I think you're not modeling your entity correctly. Since an user can have a list of friends, and also be a friend of other users, I guess you need to capture the latter part in the model.

    Since this is a many-to-many relationship, and EF Core still hasn't supported it without declaring an entity to represent the join table, you need to defind that entity as well:

    public class AppUser : IdentityUser
    {
        public int Age { get; set; }
        public string Sex { get; set; }
        public string City { get; set; }
        public string Education { get; set; }
        public string Description { get; set; }
    
        public ICollection<AppUserFriendship> FriendsOf { get; set; }
        public ICollection<AppUserFriendship> Friends { get; set; }
    }
    
    public class AppUserFriendship
    {
        public string UserId { get; set; }
        public AppUser User { get; set; }
    
        public string UserFriendId { get; set; }
        public AppUser UserFriend { get; set; }
    }
    

    And then you need to configure their relationships:

    public class ApplicationDbContext : IdentityDbContext<AppUser>
    {
        ...
    
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
    
            builder.Entity<AppUserFriendship>(b =>
            {
                b.HasKey(x => new { x.UserId, x.UserFriendId };
    
                b.HasOne(x => x.User)
                    .WithMany(x => x.Friends)
                    .HasForeignKey(x => x.UserId)
                    .OnDelete(DeleteBehavior.Restrict);
    
                b.HasOne(x => x.UserFriend)
                    .WithMany(x => x.FriendsOf)
                    .HasForeignKey(x => x.UserFriendId)
                    .OnDelete(DeleteBehavior.Restrict);
            });
        }
    
        public DbSet<AppUser> Users { get; set; }
    }
    

    Note OnDelete(DeleteBehavior.Restrict). You have to set it to something other than DeleteBehavior.Cascade which is the default to prevent the cascade deletion.

    Disclaimer: I wrote all by hand. Never test it.