InvalidOperationException:无法跟踪实体类型“ApplicationUser”的实例

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

提问人:bootsy 提问时间:7/2/2019 最后编辑:bootsy 更新时间:7/3/2019 访问量:3224

问:

完整的错误消息:

InvalidOperationException:无法跟踪实体类型“ApplicationUser”的实例,因为已跟踪具有 {'Id'} 相同键值的另一个实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。请考虑使用“DbContextOptionsBuilder.EnableSensitiveDataLogging”来查看冲突的键值。

当我尝试在“查看”页面上更新用户信息时,出现此错误。

更新代码:

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

我希望用户信息保存到数据库,并在单击“保存”按钮后在主页上显示更改。

C# ASP.NET 核心

评论

0赞 mgPePe 6/29/2020
我看不出此代码与答案中的代码之间有什么区别。如果你改变了它,那么其他人看到的是原始的错误版本,而不是修复的版本,这是有道理的。

答:

2赞 Chris Pratt 7/2/2019 #1

而且,这就是您应该使用视图模型的原因。实际上,除了这个特定的例外之外,还有其他原因。

首先,对正在发生的事情的解释。在代码库中的某个位置,在处理此请求期间,查询了与您尝试编辑的内容具有相同 ID 的实例。这可能是由许多不同的事情引起的:重要的部分是你的上下文已经在跟踪这个特定的实例。ApplicationUser

当您在操作中将帖子直接绑定到此处时,您将创建一个完全不同的实例。将该新实例直接添加到您的上下文中,也会尝试开始跟踪该实例,但会失败,因为与您的上下文已在跟踪的内容存在冲突。ApplicationUser

两点启示:

  1. 编辑实体时,请始终将其从数据库中重新拉出,根据需要对其进行更改,然后将该实例保存回数据库。

  2. 你永远不应该直接保存从帖子中创建的任何内容(即你在这里的操作参数)到你的数据库中。你不应该这样做的原因有很多,但安全是第一位的。发布数据是来自用户的数据,永远不要信任用户。user

使用视图模型可以解决这两个问题。您只需创建一个类(名称无关紧要),然后为要允许用户修改的数据添加属性。换句话说,您将排除诸如 ID(ID 应始终来自 URL)、创建日期和其他审计跟踪等内容。然后,将帖子绑定到此视图模型,从数据库中提取要修改的实际实体,将相关数据映射到该实体,然后保存该实体。ApplicationUserViewModel

总而言之,这将使您的操作看起来像:

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

评论

0赞 bootsy 7/2/2019
我认为这会起作用,但我丢失了我的编辑链接网址。它曾经是 localhost/editprofile/Edit。现在是 localhost/{id},链接断开。我需要做些什么才能取回该链接?
0赞 Chris Pratt 7/2/2019
您必须一直在使用基于约定的路由。默认路由将接受 id,因此您可以直接将属性更改回 .要坚持使用基于属性的路由,您只需要将类似的东西应用于控制器类,然后 .[HttpPost][Route("[controller]")][HttpPost("edit/{id}")]
0赞 bootsy 7/3/2019
现在我收到一个错误“InvalidOperationException:传递给 ViewDataDictionary 的模型项的类型为”FE”。Models.ApplicationViewModel“,但此 ViewDataDictionary 实例需要类型为”FE“的模型项。Models.ApplicationUser”。我在 HttpGet 上使用 ApplicationUser 进行编辑操作。更新了 OP 中的代码。
0赞 Chris Pratt 7/3/2019
视图模型也是视图需要使用的模型。
0赞 bootsy 7/3/2019
我得到同样的错误,它只是发生在获取而不是帖子上。这就是我将视图更改为 @model FE 的原因。httpget 中的所有内容都需要更改为 ApplicationViewModel 吗?