0

I want users to be able to select multiple skills from a MultiSelectList dropdown. I am able to save the multiple skill selections of each user to the database, but I discovered if I remove a skill option for one user, and I log in with a different user who previously had the same skill saved in his selections, it would have been removed for this user and every other user that had similar skill option saved.

Just so I am being clear. Let's say user A had these skills saved ["C#", "Python", "Java"]. User B current currently saved skills are ["C++","Scala"]. User B then logs and decides to add the C# he has just learnt. Once he updates his profile and his selections become this ["C++","Scala", "C#"]. C# would have been removed from User A's selections so it becomes ["Python", "Java"].

This is my custom IdentityUser class.

public class ApplicationUser : IdentityUser
{
    public ApplicationUser()
    {
        Skills = new List<Skill>();
    }
    public string Location { get; set; }
    public virtual ICollection<Skill> Skills { get; set; }
}

This is the Skill model.

public class Skill
{
    public int SkillId { get; set; }
    public string SkillType { get; set; }
}

And this is how I save the skill selections in the controller.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Profile(ProfileViewModel profileModel)
{
    var user = await _userManager.GetUserAsync(User);

    if (user == null)
    {
        return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
    }
    if (ModelState.IsValid)
    {
        if (user.Location != profileModel.Location) user.Location = profileModel.Location;

        if (profileModel.SelectedSkillIds != null)
        {
            List<Skill> tempSkills = new List<Skill> { };
            foreach (var skillID in profileModel.SelectedSkillIds)
            {
                user.Skills.Add(_context.Skills.FirstOrDefault(x => x.SkillId == skillID));
                var skill = _context.Skills.Find(skillID);
                if (skill != null)
                {
                    user.Skills.Add(skill);
                    tempSkills.Add(skill);
                }
                var allSkills = _context.Skills.ToList();
                var skillsToRemove = allSkills.Except(tempSkills);
                foreach (var sk in skillsToRemove)
                {
                    user.Skills.Remove(sk);
                }
            }
            await _userManager.UpdateAsync(user);
            await _signInManager.RefreshSignInAsync(user);
            return RedirectToAction("Profile", "Account");
        }
        return View(profileModel);
    }
}

Update - How I delete selections

if (profileModel.SelectedSkillIds != null)
{
    List<UserSkill> tempSkills = new List<UserSkill> { };
    foreach (var skillID in profileModel.SelectedSkillIds)
    {
        var skill = _context.Skills.Find(skillID);
        if (skill != null)
        {
            var userskill = new UserSkill { AppUserId = user.Id, SkillId = skill.SkillId };
            user.UserSkills.Add(userskill);
            tempSkills.Add(userskill);
        }
    var allSkills = _context.UserSkills.ToList();
    var skillsToRemove = allSkills.Except(tempSkills);
    foreach (var sk in skillsToRemove)
    {
        user.UserSkills.Remove(sk); 
    }
}
Qudus
  • 1,440
  • 2
  • 13
  • 22

1 Answers1

1

You should create an class like UserSkills that have UserId and SkillId and in this case any user can have multiple skills and any skill can use for many users. see many-to-many, 1,2

You should change your model to this

public class ApplicationUser : IdentityUser
{
    public ApplicationUser()
    {
        Skills = new List<Skill>();
    }
    public string Location { get; set; }
    public virtual ICollection<UserSkills> Skills { get; set; }
}
public class Skill
{
    public int SkillId { get; set; }
    public string SkillType { get; set; }
    public virtual ICollection<UserSkills> Skills { get; set; }
}
public class UserSkills
{
    public int Id { get; set }
    public int UserId { get; set }
    public int SkillId { get; set }
    public Skill Skill { get; set; }
    public ApplicationUser User { get; set; }
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Profile(ProfileViewModel profileModel)
{
    var user = await _userManager.GetUserAsync(User);

    if (user == null)
    {
        return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
    }
    if (ModelState.IsValid)
    {
        if (user.Location != profileModel.Location) user.Location = profileModel.Location;

        if (profileModel.SelectedSkillIds != null)
        {
            List<Skill> tempSkills = new List<Skill> { };
            foreach (var sk in user.UserSkills)
            {
               user.UserSkills.Remove(sk);
            }
            foreach (var skillID in profileModel.SelectedSkillIds)
            {
                var userSkills = new UserSkill { UserId = user.Id, SkillId = skillID };
                user.UserSkills.Add(userSkills);
            }
            await _userManager.UpdateAsync(user);
            await _signInManager.RefreshSignInAsync(user);
            return RedirectToAction("Profile", "Account");
        }
        return View(profileModel);
    }
}

Then add UserSkills to your DbContext

public DbSet<Skill> Skills { get; set; }
public DbSet<UserSkill> UserSkills { get; set; }

Finally use Add-Migration and Update-DataBase in package manager console

Another options

You can inject DbContext in your controller and add UserSkills data in UserSkill Tables

private readonly YourDbContext _dbContext;
public UserController(YourDbContext dbContext)
{
    _dbContext = dbContext;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Profile(ProfileViewModel profileModel)
{
    var user = await _userManager.GetUserAsync(User);

    if (user == null)
    {
        return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
    }
    if (ModelState.IsValid)
    {
        if (user.Location != profileModel.Location) user.Location = profileModel.Location;

        if (profileModel.SelectedSkillIds != null)
        {
            var userSkillsForDelete = _dbContext.UserSkills.Where(a => a.UserId == user.Id).ToList();//<-- NOTE THIS
            foreach (var sk in userSkillsForDelete)
            {
               //user.UserSkills.Remove(sk);
               _dbContext.UserSkills.Remove(sk);<--NOTE THIS
            }
            foreach (var skillID in profileModel.SelectedSkillIds)
            {
                var userSkills = new UserSkill { UserId = user.Id, SkillId = skillID };
               _dbContext.UserSkills.Add(userSkills);<--NOTE THIS
            }
            await _userManager.UpdateAsync(user);
            await _signInManager.RefreshSignInAsync(user);
            return RedirectToAction("Profile", "Account");
        }
        return View(profileModel);
    }
}

Farhad Zamani
  • 5,381
  • 2
  • 16
  • 41
  • Hi. The values are not being saved, I couldn't popuate the dropdown as a result. This is what I've added to my context `public DbSet Skills`. – Qudus Jul 27 '20 at 20:25
  • It is working fine now. The new issue is, for some strange reasons, if I remove some selections from the dropdown, they are not updated. I can only add. – Qudus Jul 28 '20 at 23:08
  • Update: I can now delete selections. I have updated how I achieved this in the question but i still can't understand why your approach to remove existing selections isn't working. I would appreciate a feedback if you figure it out. – Qudus Jul 29 '20 at 00:03
  • @QudusGiwa i updated my answer for update user skills. check `userSkillsForDelete` – Farhad Zamani Jul 29 '20 at 09:00
  • It works now. So, this means even if I try to reference `user.userSkills`, it still wouldn't know which userSkills I want until I pass it the UserID. – Qudus Jul 29 '20 at 22:21
  • 1
    @QudusGiwa You can use `user.userSkills` to get all user skills. but you should Include `UserSkill` data like this `await _userManager.Users.Include(a => a.UserSkills).FirstOrDefaultAsync(a => a.Id == user.Id)` – Farhad Zamani Jul 30 '20 at 13:40
  • 1
    I understand. Thanks for your help ) : – Qudus Jul 30 '20 at 21:49