Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC 5 Entity Framework - Relations

I'm currently developing a web application. I'm using Identity for the authentication and roles of my user.

I want each user in my application to have an associated "Institution". This Institution contains a Name and a Description. Here is my IdentityUser class:

    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;
        }

        [Display(Name="Institution")]
        public Institution Institution { get; set; }
    }

When I update my database, the Seed method is executed, and this one, I'm creating a User with the "admin" roles and I associate an Institution. Here is my Seed method:

if (!context.Users.Any(u => u.UserName == "mxfragz"))
{
    var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new IdentitiesDb()));
    roleManager.Create(new IdentityRole("admin"));

    var store = new UserStore<ApplicationUser>(context);
    var manager = new UserManager<ApplicationUser>(store);
    var user = new ApplicationUser { UserName = "mxfragz" };
    user.Institution = new Institution() { Name = "London", Description="Description" };
    manager.Create(user, "password");
    manager.AddToRole(user.Id, "admin");
}

My problem is that, when I'm creating a new User in my web application, I can't find a way to associate an existing Institution (here only "London" is created). With what I've done so far, when I create a user, I get the ID of the selected Institution and find an existing Institution in order to associate it with the Institution property defined in my user. When I do so, instead of associating the existing Institution that has been found, entity framework creates a new one, and associate it to my User. I end up with 2 different Institutions with the same Name and Description. Here is my code:

public async Task<ActionResult> Create(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser() { UserName = model.Username, Email = string.Empty };
        int selected = int.Parse(model.SelectedInstitution);
        user.Institution = new InstitutionsDb().Institutions.Where(x => x.Id == selected).First();
        IdentityResult result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new IdentitiesDb()));
            UserManager.AddToRole(user.Id.ToString(), roleManager.Roles.Where(x => x.Id == model.SelectedRole.ToString()).First().Name);

            return RedirectToAction("Index", "Users");
        }
        else
        {
            AddErrors(result);
        }
    }

    model.Roles = GetRoles();
    model.Institutions = GetInstitutions();
    return View(model);
}

I found several topics about using the Attach method, but even when I tried with it, it didn't work. Am I doing something wrong ? Or is there any way to do what I want to do ?

like image 710
MxFragz Avatar asked Aug 14 '26 01:08

MxFragz


1 Answers

The foreign key relationship needs to be exposed through a virtual collection on the Institution along with the actual foreign key value on the ApplicationUser.

public class ApplicationUser : IdentityUser
{
    //...

    public int InstitutionId { get; set; }

    [Display(Name="Institution")]
    [ForeignKey("InstitutionId")] //This attribute isn't strictly necessary but adds clarity
    public Institution Institution { get; set; }
}

public class Institution
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public string Description { get; set; }

    public virtual ICollection<User> Users { get; set; }
}

EF will automatically attach the associated Institution on the virtual property based on the InstitutionId shown below.

I would suggest just adding the DbSet<Institutions> to the ApplicationDbContext rather than it's own InstitutionsDb context, this may be a part of your issue because the UserManager is bound only to the ApplicationDbContext or which ever context you have configured int he IdentityConfig.cs file.

public async Task<ActionResult> Create(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser() { UserName = model.Username, Email = string.Empty };
        int selected = int.Parse(model.SelectedInstitution);

        var context = HttpContext.GetOwinContext().Get<ApplicationDbContext>()

        //Set the id
        user.InstitutionId = context.Institutions.Where(x => x.Id == selected).First().Id;          

        IdentityResult result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new IdentitiesDb()));
            UserManager.AddToRole(user.Id.ToString(), roleManager.Roles.Where(x => x.Id == model.SelectedRole.ToString()).First().Name);

            return RedirectToAction("Index", "Users");
        }
        else
        {
            AddErrors(result);
        }
    }

    model.Roles = GetRoles();
    model.Institutions = GetInstitutions();
    return View(model);
}

This should allow you to call user.Institution.Name when you retrieve the ApplicationUser from the UserManager

like image 70
Bensmind Avatar answered Aug 17 '26 00:08

Bensmind