无法更改关系,因为一个或多个外键属性不可为空
本文关键字:属性 一个 关系 因为 | 更新日期: 2023-09-27 18:13:47
更新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();
}
在实体框架中,你可以使用外键关联。也就是说,另一个对象的外键被表示为一对两个属性:一个原始外键属性(例如NominalRouting.OrderItemId
)和一个对象引用(NominalRouting.OrderItem
)。
这意味着您可以设置原始值或对象引用来建立外键关联。如果您设置了其中一个,EF会尽量使另一个保持同步。不幸的是,这也可能会导致原始外键值与其附带的引用之间的冲突。
很难说你的情况到底发生了什么。然而,我确实知道您将对象从一个父对象"复制"到另一个父对象的方法是……不是最理想的。首先,更改主键值从来都不是一个好主意。通过将它们设置为0
,您可以使对象看起来像新的,但它们不是。其次,您可以多次将相同的子对象分配给其他父对象。我认为,作为一个结果,你结束了大量的对象有一个外键值,但没有一个引用。
我说"复制",因为这似乎是你试图实现的。如果是这样,您应该正确地克隆对象并将它们Add
到每个targetOrderItem
。同时,我想知道为什么你(显然)克隆所有这些对象。看起来多对多关联在这里更合适。但那是另一个话题。
现在你的实际问题是:如何找到冲突的关联?
那非常非常难。它需要代码搜索概念模型并查找外键关联中涉及的属性。然后你必须找到它们的值并找出不匹配。这很难,但是与确定可能的冲突何时是实际的冲突相比就微不足道了。让我用两个例子来说明这一点。这里,类OrderItem
有一个必需的外键关联,由属性Order
和OrderId
组成。
var item = new OrderItem { OrderId = 1, ... };
db.OrderItems.Add(item);
db.SaveChanges();
所以有一个项目OrderId
被分配并且Order
= null, EF很高兴。
var item = db.OrderItems.Include(x => x.Order).Find(10);
// returns an OrderItem with OrderId = 1
item.Order = null;
db.SaveChanges();
再一次,一个项目的OrderId
被赋值并且Order
= null,但是EF抛出异常" the relationship cannot be changed…"
(还有更多可能的冲突情况)
所以在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
是一个内部类,所以你只能在调试器中检查集合来了解冲突的实体。仅用于诊断目的!!