实体框架:对象状态管理器中已存在具有相同键的对象

本文关键字:对象 存在 框架 状态 管理器 实体 | 更新日期: 2023-09-27 18:36:50

我看到这个问题已经被问了很多,但是我还没有找到任何可以解决我遇到的问题的东西。

显然,我正在使用实体框架对记录执行更新。但是,一旦更新完成,每当我尝试保存时,我都会收到以下错误消息:

An object with the same key already exists in the objectstatemanager

起初,我从包含ZipCodeTerritory模型对象副本的视图中传入一个集合对象zipToUpdate。我通过拉出此对象并仅发送相关字段来更改代码。但是,我仍然遇到相同的错误。

同样奇怪的是我第一次运行这段代码时,它工作正常。之后的任何尝试我都会收到错误。

控制器

这是调用编辑函数的方法中的代码

public static string DescriptionOnly(ZipCodeIndex updateZip)
{
    if (!string.IsNullOrWhiteSpace(updateZip.newEffectiveDate) || !string.IsNullOrWhiteSpace(updateZip.newEndDate))
    {
        return "Neither effective or end date can be present if updating Territory Code only; ";
    }
    _updated = 0;
    foreach (var zipCode in updateZip.displayForPaging.Where(x => x.Update))
    {
        ProcessAllChanges(zipCode, updateZip.newTerritory, updateZip.newStateCode, updateZip.newDescription, updateZip.newChannelCode);
    }
    _msg += _updated + " record(s) updated; ";
    return _msg;
}

这是实际进行更新的方法。

private static void ProcessAllChanges(ZipCodeTerritory zipToUpdate, string newTerritory, string newStateCode, string newDescription, string newChannelCode)
{
    try
    {
        if (!string.IsNullOrWhiteSpace(newTerritory)) zipToUpdate.IndDistrnId = newTerritory;
        if (!string.IsNullOrWhiteSpace(newStateCode)) zipToUpdate.StateCode = newStateCode;
        if (!string.IsNullOrWhiteSpace(newDescription)) zipToUpdate.DrmTerrDesc = newDescription;
        if (!string.IsNullOrWhiteSpace(newChannelCode)) zipToUpdate.ChannelCode = newChannelCode;
        if (zipToUpdate.EndDate == DateTime.MinValue) zipToUpdate.EndDate = DateTime.MaxValue;
        _db.Entry(zipToUpdate).State = EntityState.Modified;
        _db.SaveChanges();
        _updated++;
    }
    catch (DbEntityValidationException dbEx)
    {
        _msg += "Error during update; ";
        EventLog.WriteEntry("Monet", "Error during ProcessAllChanges: " + zipToUpdate.ToString() + " |EX| " + dbEx.Message);
    }
    catch (Exception ex)
    {
        _msg += "Error during update; ";
        EventLog.WriteEntry("Monet", "Error during ProcessAllChanges: " + zipToUpdate.ToString() + " |MESSAGE| " + ex.Message);
    }
}

编辑

ZipCodeIndex对象包含ZipCodeTerritory模型对象的列表。这些不是从 linq 查询中提取的,而只是从视图传递回控制器。下面是启动该过程的控制器方法的签名:

[HttpPost]
public ActionResult Update(ZipCodeIndex updateZip, string button)

实体框架:对象状态管理器中已存在具有相同键的对象

这是由于数据库上下文的处置不当,因为您永远不会处置上下文对象本身,因此会遇到此类问题。

我建议将代码包装在 using 语句中。

using (var context = new MyContext())
{
   // Do my save code here...
}

这将确保正确处理上下文!

当我遇到这种类型的问题时 - 我还没有看到这个确切的问题,但有很多类似的问题 - 这是因为我在一个上下文中检索了我的实体,并且我试图使用另一个上下文保存它们,或者因为发生了一些事情导致实体框架失去对它们的跟踪 - 将它们从数据存储中拉出GetAsNoTracking会产生这种效果, 尽管如果您知道不会更改提供明显性能优势的实体。

这样做的结果是,EF 认为您正在保存一个新实体,该实体恰好具有它已经知道的实体的大部分相同属性,而不是对它熟悉的实体进行更改。

从您的代码中,我无法告诉您确切的解决方案是什么,但我会通过密切关注您检索实体的位置并确保通过相同的上下文保存更改来开始解决此问题。

您的编辑表明您正在将新的ZipCodeTerritory对象从视图向下传递到控制器。这些条目可能映射到存储中的现有ZipCodeTerritory条目,因此这里发生的事情是,因为它们不是来自数据库,而是正确类型的实体,因此数据上下文将它们视为新对象。它不知道您正在更新现有记录,因为它没有创建它们,因此它不保留对从视图返回的这些对象的跟踪引用。

现在可能有一个更优雅的解决方案 - 我目前正在使用 4.5,所以我还没有看过最近的 EF 行为 - 但一个简单的解决方案是从数据存储中检索匹配的ZipCodeTerritory对象,然后更新它们并保存回来。在这种情况下,尽管您要传回的对象属于ZipCodeTerritory类型,但它们的行为更像数据传输对象,这是造成此处混淆的部分原因 - 因为它们是由视图创建的(尽管来自数据存储中的数据),您的数据上下文根本无法识别它们。