Updating user role using asp.net identity

2019-03-21 14:03发布

问题:

I have the following problem. While using the following code below to change the user's current role i am getting an exception with the message like below:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public virtual ActionResult Edit(User user, string role)
    {
        if (ModelState.IsValid)
        {
            var oldUser = DB.Users.SingleOrDefault(u => u.Id == user.Id);
            var oldRoleId = oldUser.Roles.SingleOrDefault().RoleId;
            var oldRoleName = DB.Roles.SingleOrDefault(r => r.Id == oldRoleId).Name;
            if (oldRoleName != role)
            {
                Manager.RemoveFromRole(user.Id, oldRoleName);
                Manager.AddToRole(user.Id, role);
            }
            DB.Entry(user).State = EntityState.Modified;

            return RedirectToAction(MVC.User.Index());
        }
        return View(user);
    }

Attaching an entity of type 'Models.Entities.User' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

Does anybody know a good solution to this problem ?

回答1:

The problem is that your Manager and DB doesn't use the same DbContext. So when you send an user from the context of your DB to the Manager it will handle it as a "new" one - and then you cant remove it from the role. You have two ways to go here. The easiest is to get the User from your Manager.

[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult Edit(User user, string role)
{
    if (ModelState.IsValid)
    {
        // THIS LINE IS IMPORTANT
        var oldUser = Manager.FindById(user.Id);
        var oldRoleId = oldUser.Roles.SingleOrDefault().RoleId;
        var oldRoleName = DB.Roles.SingleOrDefault(r => r.Id == oldRoleId).Name;

        if (oldRoleName != role)
        {
            Manager.RemoveFromRole(user.Id, oldRoleName);
            Manager.AddToRole(user.Id, role);
        }
        DB.Entry(user).State = EntityState.Modified;

        return RedirectToAction(MVC.User.Index());
    }
    return View(user);
}

The more elegant way is to start using an DI-framework like AutoFac (https://code.google.com/p/autofac/wiki/MvcIntegration) and set your DbContext as InstancePerApiRequest.

builder.RegisterType<YourDbContext>().As<DbContext>().InstancePerApiRequest();


回答2:

My roles are managed in the seed method of my DbMigrationsConfiguration class and I renamed it like this:

        if (context.Roles.Any(r => r.Name == "User"))
        {
            var store = new RoleStore<IdentityRole>(context);
            var manager = new RoleManager<IdentityRole>(store);
            var role = manager.Roles.First(r => r.Name == "User");

            role.Name = "NewNameForUser";
            manager.Update(role);
        }