【问题标题】:EF attaching object to context in foreach working only first timeEF将对象附加到foreach中的上下文仅第一次工作
【发布时间】:2019-09-13 10:04:12
【问题描述】:

我有一个对象列表,我需要将这些对象附加到 Context 以跟踪更改并随后保存它们,但迭代项目的 foreach 仅在第一次执行,之后方法结束。 我确定这些项目已经存在于数据库中。

我已经尝试调用.Attach() 方法并将Entry 状态设置为Unchanged。


protected override Task SetViewModelPropertiesAsync()
        {

            SelectedItems.ForEach(l =>
            {
                //Context.Pap_Pedido_ODP.Attach(l);
                Context.Entry(l).State = System.Data.Entity.EntityState.Unchanged;
                // After the first iteration the method ends
            });


            return base.SetViewModelPropertiesAsync();
        }

我希望将所有项目都添加到上下文中,但在 foreach 循环的第一次迭代之后会中断该方法并继续下一个方法,甚至没有给出异常。

编辑: 当我执行 Attach 或 EntityState 时,会跳过 foreach 之后的更多代码。 如果我评论两个代码都正确执行

【问题讨论】:

  • 也许收藏中只有一项。
  • 该项目很可能有一个主键,所以它只会被添加一次。
  • @Bigabdoul 不,我知道至少有 2 项,并且我已经调试检查过
  • @jdweng 每个项目都有自己的主键,但可怕的是 整个方法 中断。 foreach 之后还有更多代码(我没有包含在问题示例中)并且它被跳过了
  • 我见过这样的问题。花时间单步执行代码后,我通常会发现异常。当 C# 发现没有异常处理程序的异常时,它似乎会向上移动执行堆栈并在 Ancestor 方法中找到第一个异常处理程序。异常处理程序可以是祖先中的任何位置。因此,当我在异常之后单步执行代码时,它会转到父方法,然后疯狂地跳转到另一段代码中的异常处理程序。为了解决这个问题,我添加了一个新的异常处理程序。

标签: c# .net wpf entity-framework


【解决方案1】:

这种行为听起来确实像是抛出了异常。这是 IMO 关于List<T>.ForEach() 的一个巨大危险信号,也是我从不使用它的主要原因。如果您将代码更改为:

foreach(var item in SelectedItems)
{
   Context.Pap_Pedido_ODP.Attach(item);
   Context.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
}

...您现在至少应该看到阻止您的代码的异常。在上下文之间附加/分离实体是混乱的,而且我个人可以证明它的情况非常非常少。您正在处理对实体的引用。这意味着:

  1. 项目不得已与任何其他上下文相关联。
  2. 上下文不得已经有另一个实体与项目具有相同的 PK 跟踪。

第 1 点会妨碍您,因为任何返回“可能”想要将该实体附加到另一个上下文的实体的代码都需要分离,或者以其他方式加载该实体 AsNoTracking。从某个地方将跟踪的实体传递给您的方法会破坏您的代码。

第 2 点会阻碍你,因为即使传递的实体是分离的,如果你的上下文碰巧已经通过另一个引用知道该实体,你必须基本上丢弃那个未跟踪的实体,并使用对跟踪实例的引用。这意味着在附加任何实体之前,您需要检查 Context .Local 是否有匹配的实体。

只有当实体没有被追踪,并且上下文中没有被追踪的具有相同 PK 的实体时,你才能附加它。

如果您的代码没有中断异常并且您正在调试,请确保您的调试异常处理设置为中断所有异常。或者,您可以在 catch 中弹出一个带有断点的 try/catch 块来检查异常。

编辑:检查实例

foreach(var item in SelectedItems)
{
   if(Context.Pap_Pedido_ODP.Local.Contains(item))
   { // This exact instance is already associated to the Context.
     // We shouldn't need to copy anything across or do anything...
   }
   else
   {
      var existingItem = Context.Pap_Pedido_ODP.Local.SingleOrDefault(x => x.Id == item.Id);
      if(existingItem != null)
      { // A different instance matching this one already exists in the context, 
        // Here if item represents changes we would need to copy changes across to existingItem...
      }
      else
      { // Item is not associated, safe to attach.
          Context.Pap_Pedido_ODP.Attach(item);
          // ...
      }
   }
}

现在它并没有就此结束。如果“item”包含对其他实体的引用,每个实体都会自动更新。如果其中一些已经与上下文相关联,这可能会导致问题。当 DbContext 的生命周期过长或引用实体的同一实例的多个副本被传回时,可能会导致这种情况。例如,如果我保存了一组订单,并且订单包含对客户的引用。 2 个订单引用了同一客户。当我将订单 #1 附加到客户 #1 时,客户 1 现在与上下文相关联。当我尝试附加订单#2 时,客户#1 的实例与订单#1 不同,因此附加订单#2 会产生错误。在处理分离实体时,您需要采取措施确保图中引用同一记录的对象的所有实例都使用相同的对象实例引用。当您从 EF 加载数据时,这些将是相同的对象引用,但如果您将它们提供给序列化器/反序列化器,您将获得 2 个相同的副本作为单独的引用。您不能简单地重新附加这些对象引用。

不幸的是,我无法提供真正简单的答案来使其更容易,但这就是为什么序列化和反序列化实体可能是一个糟糕的想法,甚至分离/附加实例也会很痛苦。

【讨论】:

  • 如您所说,更改List<T>.ForEach() 引发了导致问题的异常。但我不知道它为什么被抛出,如果我在调用Context.SaveChanges() 时不附加它,我会得到另一个异常,说有些元素不在上下文中,但是如果我添加附加它们,我会得到另一个异常说元素已经在上下文中。
  • 这可能是附加和未附加参考的组合。如果对象 A 引用了 B 的实例,并且每个 B 都引用了 C 的实例,则需要根据上下文检查链中的每个实例。附加和分离对象图是一件很麻烦的事情。
  • 我已经扩展了答案以涵盖附加实体时需要注意的一些事项。
  • 谢谢!显然这是问题所在,我已经签入了Local 关联实体,它就在那里。我最终传递了 ID 并查询它们以获取它们。
  • 是的。看起来重新附加是节省时间的好选择,因为数据已经在某一时刻加载,但它的麻烦远大于它的价值。通过 ID 加载实体很快,如您所见,重新附加分离的实体可能会出现问题,并且分离的实体可能在最初读取它们和您去更新它们之间是陈旧的。 (其他人编辑了数据)它有一个位置,但不应该是持久性的默认设计。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-05
  • 1970-01-01
  • 1970-01-01
  • 2020-06-23
  • 1970-01-01
相关资源
最近更新 更多