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

在使用EF更新期间出现以下错误:

操作失败:无法更改关系,因为一个或多个外键属性不可为空。 当对关系进行更改时,相关的外键属性将设置为空值。 如果外键不支持空值,则必须定义新关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

有没有一般的方法来查找哪些外键属性导致上述错误?

[更新]

对于以下代码导致上述错误的一种情况(我在断开连接的环境中工作,所以我使用graphdiff更新我的对象图),当它想要运行_uow.Commit();

 public void CopyTechnicalInfos(int sourceOrderItemId, List 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(); 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(); } 

在Entity Framework中,您可以使用外键关联 。 也就是说,另一个对象的外键表示为一对两个属性:原始外键属性(例如NominalRouting.OrderItemId )和对象引用( NominalRouting.OrderItem )。

这意味着您可以设置原始值或对象引用以建立外键关联。 如果您设置其中一个,EF会尽可能保持另一个同步。 不幸的是,这也可能引起原始外键值与其伴随参考之间的冲突。

很难说出你的情况究竟发生了什么。 但是,我知道你将对象从一个父母“复制”到另一个父母的方法是……不理想。 首先,更改主键值永远不是一个好主意。 通过将它们设置为0您可以使对象看起来像新的,但它们不是。 其次,您将多个相同的子对象分配给其他父对象。 我认为 ,因此,您最终会得到大量具有外键值但不是引用的对象

我说“复制”,因为那是你看似试图实现的。 如果是这样,您应该正确克隆对象并将它们Add到每个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抛出exception“关系无法更改…”。

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

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

但是有一个肮脏的伎俩。 当EF尝试匹配外键值和引用时,在嵌套树的深处某处,它会收集我们正在讨论的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是一个HashSetEntityEntry是一个内部类,因此您只能检查调试器中的集合以了解冲突的实体。 仅用于诊断目的!