由于一个或多个外键属性是不可为空的,因此无法更改关系。

30
在使用EF进行更新时,我遇到了以下错误:
“操作失败:由于一个或多个外键属性是非空的,因此无法更改关系。当对关系进行更改时,相关的外键属性将设置为空值。如果外键不支持空值,则必须定义新关系,将外键属性分配给另一个非空值,或删除不相关的对象。”
有没有一种通用方法可以找到哪些外键属性导致上述错误?
【更新】
在某种情况下,以下代码会导致上述错误(我在脱机环境中工作,因此使用了graphdiff来更新我的对象图),当它要运行_uow.Commit()时:
public void CopyTechnicalInfos(int sourceOrderItemId, List<int> targetOrderItemIds)
{
  _uow = new MyDbContext();
   var sourceOrderItem = _uow.OrderItems
          .Include(x => x.NominalBoms)
          .Include("NominalRoutings.NominalSizeTests")
          .AsNoTracking()
          .FirstOrDefault(x => x.Id == sourceOrderItemId);


   var criteria = PredicateBuilder.False<OrderItem>();
   foreach (var targetOrderItemId in orderItemIds)
   {
      int id = targetOrderItemId;
      criteria = criteria.OR(x => x.Id == id);
   }
   var targetOrderItems = _uow.OrderItems
                              .AsNoTracking()
                              .AsExpandable()   
                              .Where(criteria)
                              .ToList();

  foreach (var targetOrderItem in targetOrderItems)
  {
        //delete old datas and insert new datas 
        targetOrderItem.NominalBoms = sourceOrderItem.NominalBoms;
        targetOrderItem.NominalBoms.ForEach(x => x.Id = 0);

        targetOrderItem.NominalRoutings = sourceOrderItem.NominalRoutings;
        targetOrderItem.NominalRoutings.ForEach(x => x.Id = 0);
        targetOrderItem.NominalRoutings
                       .ForEach(x => x.NominalTests.ForEach(y => y.Id = 0));
        targetOrderItem.NominalRoutings
                       .ForEach(x => x.NominalSizeTests.ForEach(y => y.Id = 0));
       _uow.OrderItems.UpdateGraph(targetOrderItem, 
                                   x => x.OwnedCollection(y => y.NominalBoms)
                                         .OwnedCollection(y => y.NominalRoutings, 
                                          with => with
                                         .OwnedCollection(t => t.NominalTests)));
   }
   _uow.Commit();
}

你能不能缩小代码范围并在这里发布?我(以及其他一些人)从未遇到过这种异常,因此我们需要更多上下文来诊断问题。 - Hopeless
@Hopeless:我的意思是通常我该如何找到导致错误的外键属性。但如果需要,我也可以发布我的代码。 - Masoud
@masoud,我在这里找到了答案:https://stackoverflow.com/questions/22858491/entity-framework-remove-object-with-foreign-key-preserving-parent - antonio
1个回答

64
在 Entity Framework 中,您可以使用外键关联。也就是说,一个指向其他对象的外键由两个属性组成:一个基元外键属性(例如 NominalRouting.OrderItemId)和一个对象引用(NominalRouting.OrderItem)。
这意味着您可以设置原始值或对象引用来建立外键关联。如果您设置其中之一,EF 将尝试在可能的情况下保持另一个同步。不幸的是,这也可能导致基元外键值及其相应引用之间的冲突。
很难确定在您的情况下究竟发生了什么。但是,我知道“复制”一个父对象到另一个父对象这种方法并不理想。首先,更改主键值永远都不是好主意。通过将它们设置为 0,您使对象看起来像新的,但实际上并不是这样。其次,您多次将相同的子对象分配给其他父对象。我认为,因此,您最终会得到许多具有外键值但没有引用的对象。
我说“复制”,因为那似乎是您尝试实现的内容。如果是这样,您应该正确克隆对象并将其添加到每个 targetOrderItem 中。与此同时,我想知道为什么您(显然)要克隆所有这些对象。这看起来像是适合使用多对多关联的情况。但那是另一个话题。
现在您的实际问题是:如何找到冲突的关联?
那非常非常困难。它需要在概念模型中搜索代码并查找涉及外键关联的属性。然后,您必须找到它们的值并找到不匹配之处。这已经很困难了,但与确定可能的冲突何时是实际冲突相比,这就变得微不足道了。让我通过两个示例澄清这一点。在此示例中,类 OrderItem 具有要求的外键关联,由属性 OrderOrderId 组成。
var item = new OrderItem { OrderId = 1, ... };
db.OrderItems.Add(item);
db.SaveChanges();

所以有一个被分配了 OrderIdOrder 等于 null 的项目,EF 很开心。

var item = db.OrderItems.Include(x => x.Order).Find(10);
// returns an OrderItem with OrderId = 1
item.Order = null;
db.SaveChanges();

再次出现了一个被赋予OrderIdOrder为null的项目,但EF抛出异常“无法更改关系...”。

(还有更多可能的冲突情况)

因此,仅查找OrderId/Order对中不匹配的值是不够的,您还需要检查实体状态,并确切地知道在哪种状态组合中不允许不匹配。 我的建议:忘记它,修复你的代码。

虽然有一个卑鄙的把戏。当EF尝试匹配外键值和引用时,在嵌套的一系列if语句中的某个地方,它会将我们谈论的冲突收集到ObjectStateManager的一个成员变量_entriesWithConceptualNulls中。通过进行一些反射,可以获得它的值:

#if DEBUG

db.ChangeTracker.DetectChanges(); // Force EF to match associations.
var objectContext = ((IObjectContextAdapter)db).ObjectContext;
var objectStateManager = objectContext.ObjectStateManager;
var fieldInfo = objectStateManager.GetType().GetField("_entriesWithConceptualNulls", BindingFlags.Instance | BindingFlags.NonPublic);
var conceptualNulls = fieldInfo.GetValue(objectStateManager);

#endif

conceptualNulls是一个HashSet<EntityEntry>EntityEntry是一个内部类,所以您只能在调试器中检查集合以了解冲突实体的概念。仅供诊断目的!!!


谢谢你的回答,你说“更改主键值从来不是一个好主意”,并且“你应该正确地克隆对象...”,在这两种情况下结果是相同的,为什么你认为第二种方法更好? - Masoud
通常情况下,实体最好是可单独识别的对象,这样EF才能正确地跟踪它们。我不知道你所说的“这两种情况”是什么意思。如果这意味着你尝试了其他方法,我们最好在另一个问题中讨论它。这个问题关注如何查找(而不是修复)外键错误。 - Gert Arnold
2
非常感谢这个“肮脏的技巧”!没有它,我很难检测到与冲突有关的实体。 - reexmonkey
2
这个小技巧非常好,帮助我大大缩小了问题范围。谢谢! - Alex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接