ASP.NET MVC 5 Entity Framework - Relationships

I am currently developing a web application. I am using Identity to authenticate and my user role.

I want every user of my application to have an Institution associated with it. This Institution contains a Name and 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 create a user with the role "admin" and I link 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 create a new user in my web application, I cannot find a way to link the existing Institution (only "London" is created here). What I have done so far, when I create a user, I get the ID of the selected institution and find an existing Institution to associate with the Institution property defined in my user. When I do this, instead of linking the existing Institution that was found, the entity framework creates a new one and associates it with my user. As a result, I have two different institutes 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 threads about using the Attach method, but even when I tried with it it didn't work. Am I doing something wrong? Or is there a way to do what I want to do?

+3


source to share


1 answer


The foreign key relationship must be exposed through a virtual collection in the Institution, along with the actual foreign key value in 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 automatically attaches the associated Installation to a virtual property based on the InstitutionId shown below.



I would suggest just adding DbSet<Institutions>

to ApplicationDbContext

and not to your own context InstitutionsDb

, this may be part of your problem, because UserManager

only tied to ApplicationDbContext

or which ever context, you set up the int he file IdentityConfig.cs

.

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 will allow you to call user.Institution.Name

when you fetch ApplicationUser

fromUserManager

+1


source







All Articles