简体   繁体   中英

InvalidOperationException: The instance of entity type 'ApplicationUser' cannot be tracked

Complete error message:

InvalidOperationException: The instance of entity type 'ApplicationUser' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

I get this error when I try to update user info on View page.

Updated code:

  [HttpGet]
    public ActionResult Edit(string id)
    {
        //Get user and return the Edit View
        ApplicationViewModel model = db.Users.Where(u => u.Id == id)
      .Select(u => new ApplicationViewModel()
      {             
          UserName = u.UserName,
          ClearTextPassword = u.ClearTextPassword,            
          PhoneNumber = u.PhoneNumber,             
          Enabled = u.Enabled

            // Add the remainder properties
        }).FirstOrDefault();
        return View(model);
    }


    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(string id, ApplicationViewModel model)
    {
        if (ModelState.IsValid)
            return View(model);

        var user = await _userManager.FindByIdAsync(id);
        if (user == null)
            return NotFound();

        user.UserName = model.UserName;
        user.ClearTextPassword = model.ClearTextPassword;
        user.Enabled = model.Enabled;
        user.PhoneNumber = model.PhoneNumber;

        {
            var result = await _userManager.UpdateAsync(user);
            if (result.Succeeded)
            {
                //db.Entry(listdata).State = EntityState.Modified;
                //db.SaveChanges();
                return RedirectToAction("Index");
            }
        }
        return View(user);
    }

I expect the user info to save to the database and show the change on the Home page after clicking Save button.

And, this is why you should use view models. There's actually additional reasons besides just this one specific exception.

First, the explanation of what's happening. Somewhere in your codebase, during the processing of this request an instance of ApplicationUser with the same id as what you're attempting to edit was queried. This could have been caused by any number of different things: the important part is that your context is already tracking this particular instance.

When you bind the post directly to ApplicationUser here in your action, you're creating an entirely different instance. Adding that new instance directly to your context, attempts to start tracking that instance as well and fails because there's conflict with what your context is already tracking.

Two takeaways:

  1. When editing an entity ALWAYS pull it fresh from the database, alter it as necessary, and then save that instance back to the database.

  2. You should NEVER directly save anything created from the post (ie the user param of your action here) to your database. There's a whole host of reasons why you should not do this, but security is first and foremost. Post data is data from the user and NEVER trust the user.

Using a view model fixes both of these issue. You simply create a class like ApplicationUserViewModel (the name doesn't matter) and then add properties to that only for the data you want to allow the user to modify. In other words, you'd exclude things like an ID (IDs should ALWAYS come from the URL), created date and other audit trail stuff, etc. Then, you bind the post to this view model, pull the actual entity you want to modify from the database, map the relevant data onto that entity and then save that entity.

Added altogether, that would make your action look something like:

[HttpPost("{id}"]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, ApplicationUserViewModel model)
{            
    if (!ModelState.IsValid)
        return View(model);

    var user = await _userManager.FindByIdAsync(id);
    if (user == null)
        return NotFound();

    user.FirstName = model.FirstName;
    user.LastName = model.LastName;
    // etc. You can also use a mapping library like AutoMapper instead

    var result = await _userManager.UpdateAsync(user);
    if (result.Succeeded)
    {
        // db.Entry(listdata).State = EntityState.Modified;
        // db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(model);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM