【问题标题】:Update and Add Child Rows at same time with Entity Framework使用实体框架同时更新和添加子行
【发布时间】:2013-01-03 14:15:39
【问题描述】:

我在同时修改和添加子行时遇到了一些麻烦。我正在使用答案中的技术:stackoverflow.com/questions/5557829/...

问题出在以下代码中:

public void EditReport(tbl_inspection inspection)
{
    foreach (var roll in inspection.tbl_inspection_roll)
    {                    
        container.tbl_inspection_roll.Attach(roll);
        container.ObjectStateManager.ChangeObjectState(roll, (roll.id_inspection_roll == 0) ? EntityState.Added : EntityState.Modified);
    }

    container.SaveChanges();
}

我总是至少有 1 行要更新。当我有 1 行要添加时,它工作正常,问题是当我尝试同时添加 1 行以上时,显示众所周知的错误:

ObjectStateManager 中已存在具有相同键的对象。 ObjectStateManager 无法跟踪具有相同的多个对象 键。

感觉好像我在这里错过了什么......

【问题讨论】:

  • 1) 检查您的 tbl_inspection 列表以确保没有重复项。 2) 在调试器中,检查 tbl_inspection 列表中的这些记录的状态是什么在您附加它们之前,并查看其中一个是否已被视为附加。 3) 同样在调试器中,检查本地集合中是否也找到任何 tbl_inspection 记录。那件事以前发生在我身上......
  • 我检查了你描述的所有项目,看起来很好,虽然当我尝试添加超过 1 行时,id_inspection_roll 都等于零。这意味着他们是平等的?有没有办法区分它们?非常感谢您帮助我。

标签: c# asp.net entity-framework


【解决方案1】:

我认为您需要将修改的内容与添加的内容分开。在您链接到 Ladislav 的问题中,有以下代码作为示例:

if (myEntity.Id != 0)
{
    context.MyEntities.Attach(myEntity);
    context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
    context.MyEntities.AddObject(myEntity);
}

context.SaveChanges();

我认为是专门使用 Attach 而不是使用 AddObject 是导致错误的原因。

编辑:请尝试以下附加部分:

var r = new tbl_inspection_roll { id_inspection_roll = roll.id_inspection_roll };
container.tbl_inspection_roll.Attach(r);
container.Entry(r).CurrentValues.SetValues(roll);

【讨论】:

  • 仍然无法正常工作:( ...我想我必须添加一些参考或类似的东西...我尝试设置 tbl_inspection_rollReference.EntityKey 但仍然无法正常工作...跨度>
  • 哪条线失败了?附件?
  • 在附加之前,是说实体已分离,还是已经附加?
  • 您在本地收藏中找到该记录了吗?如果在本地集合中找到它,那么它将爆炸,因为它已经知道要跟踪该记录。如果它不存在,那么我有一个不同的解决方案。
  • 不不,它最初来自 html 表单,然后我使用存根创建整个对象并传递给我发布的函数......我之前对这些实体进行了一些查询,但我使用MergeOption.NoTracking...
【解决方案2】:

这里的问题是因为两个或多个子存根具有相同的键:0。一旦您尝试附加第一个对象,它就会触发错误。

必须使用某种 DTO 重新设计该方法(我认为将 ViewModel 对象传递给域模型层是不正确的,这就是我使用存根的原因)。或者直接从控制器调用一个函数来添加/修改。

编辑:

代码如下:

public void EditReport(Inspection obj)
{
    var inspection = new tbl_inspection
    {
        id_inspection = obj.ID,
        code = obj.Code       
    };

    foreach (var roll in obj.Rolls)
    {                    
        var rollStub = new tbl_inspection_roll
        {
            id_inspection_roll = roll.ID,
            id_inspection = obj.ID,
            description = roll.Description
        };

        container.tbl_inspection_roll.Attach(roll);
        container.ObjectStateManager.ChangeObjectState(roll, (roll.id_inspection_roll == 0) ? EntityState.Added : EntityState.Modified);
    }

    container.tbl_inspection.Attach(inspection);
    container.ObjectStateManager.ChangeObjectState(inspection, EntityState.Modified);

    container.SaveChanges();
}

欢迎任何更好的解决方案...

【讨论】:

  • 所以 EF 不会在 container.tbl_inspection_roll.Attach(roll) 处 Id = 0 的多个卷中犹豫不决?
  • 不...如果您有一个完整的填充存根实体,其中包含父对象及其子对象,它只会触发错误...这对我来说也没有意义...但是可以方式......现在我有一个设计问题......我应该将什么样的对象传递给这个函数......以及我应该将它保留在哪一层......
  • 我不知道这是否适合您,但我的控制器将 DTO 作为参数并返回。控制器负责将 DTO 转换为存根实体并将它们传递给在存储库对象上调用的方法。存储库对象处理我的 DbContext 的所有附加/添加/分离。我将用您的代码替换我的存储库类中存在的答案中的代码,看看它是如何工作的。
  • 我使用的是视图模型而不是 DTO,它位于与 MVC 应用程序分离的项目中......然后我的控制器创建了存根并传递给存储库上的方法。但是一旦我必须将对象直接传递到存储库,我不知道该使用哪种方法......您是否也将 DTO 保留在 MVC 应用程序中?
  • 我有一个包含我的实体的 MVC 应用程序项目和一个包含我的 DTO 的可移植类库项目。 DTO 项目是可移植的,因为我的 MVC 应用程序和 Silverlight 客户端应用程序都使用它。在服务器端和客户端使用相同的 DTO 库使 JSON(反)序列化更容易。控制器将传入的 JSON 反序列化为 DTO,将 DTO 转换为存根实体,将存根实体传递到存储库,将生成的非存根实体转换回 DTO,然后在响应中将 DTO 序列化为 JSON。
【解决方案3】:

如果我正确理解您的问题,您正在尝试保存包含 ParentObject 和相关 ChildObjects 的对象图,并且您收到异常,因为 Attach() 无法协调数据库中 ChildObjects 之间的 Id 冲突和附加父对象。

以下是我过去使用过的解决方法。它不漂亮,但它完成了工作。

public ParentObject Upsert(ParentObject newParentObject)
{
    // Check for an existing ParentObject.
    ParentObject exisitingParentObject = container.ParentObjects.SingleOrDefault(o => o.Id == newParentObject.Id);

    // If not an existing ParentObject
    if (exisitingParentObject == null)
    {
        // Add a new ParentObject
        exisitingParentObject = container.ParentObjects.Add(container.ParentObjects.Create());
    }

    // Update the properties of the existing ParentObject with the values of the new ParentObject
    var parentEntry = container.Entry(exisitingParentObject);
    parentEntry.CurrentValues.SetValues(newParentObject);

    // Remove ChildObjects that are in the existing but not in the new.
    for (int i = exisitingParentObject.ChildObjects.Count() - 1; i >= 0; i--)
    {
        ChildObject exisitingChildObject = exisitingParentObject.ChildObjects[i];
        if (!newParentObject.ChildObjects.Any(o => o.Id == exisitingChildObject.Id))
        {
            container.ChildObjects.Remove(exisitingChildObject);
        }
    }

    // Upsert new ChildObjects
    foreach (ChildObject newChildObject in newParentObject.ChildObjects)
    {
        // Check for an existing ChildObject
        ChildObject exisitingChildObject = exisitingParentObject.SingleOrDefault(o => o.Id == newChildObject.Id);

        // If not an existing ChildObject
        if (exisitingChildObject == null)
        {
            // Add a new ChildObject
            exisitingChildObject = exisitingParentObject.ChildObjects.Add(container.ChildObject.Create());
        }

        var childEntry = container.Entry(exisitingChildObject);
        childEntry.CurrentValues.SetValues(newChildObject);
    }

    container.SaveChanges();

    return exisitingParentObject;
}

【讨论】:

  • 是的。我对不得不这样做并不感到兴奋,但我知道它有效。如果其他人有更清洁的方法,我也很想听听。
  • 我不知道您调用 Attach() 的上下文,但我从 ApiController 中的 Put 操作调用它,因此我的实体本质上是一个存根。你怎么让它不是存根,一旦它不是存根,为什么你还需要调用 Attach() 呢?
  • 我正在使用 mvc...所以我在控制器操作中创建存根对象并传递给我发布的函数...一旦它分离,我必须附加它...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多