无法更改关系,因为一个或多个外键属性不可为空
The relationship could not be changed because one or more of the foreign-key properties is non nullable
我在使用 EF 更新期间遇到以下错误:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
是否有任何一般方法来查找导致上述错误的外键属性?
[更新]
对于以下代码导致上述错误的一个案例(我在断开连接的环境中工作,所以我使用 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();
}
在 Entity Framework 中,您可以使用 外键关联 。也就是说,另一个对象的外键表示为一对两个属性:原始外键属性(例如NominalRouting.OrderItemId
)和对象引用(NominalRouting.OrderItem
)。
这意味着您可以设置原始值或对象引用来建立外键关联。如果您设置其中之一,EF 会尽可能使另一个保持同步。不幸的是,这也可能导致原始外键值与其附带的引用之间发生冲突。
很难说出您的情况到底发生了什么。但是,我 do 知道你的 "copying" 对象从一个父对象到另一个父对象的方法是......不理想。首先,更改主键值绝不是一个好主意。通过将它们设置为 0
您可以使对象看起来像新的,但它们不是。其次,您多次将相同的子对象分配给其他父对象。我 认为 因此,你最终会得到大量具有外键 value 而不是 的对象参考.
我说的是"copying",因为这似乎是您想要达到的目标。如果是这样,您应该正确克隆对象并将它们 Add
到每个 targetOrderItem
。同时,我想知道你为什么(显然)克隆所有这些对象。看起来多对多关联在这里更合适。但那是另一回事了。
现在您的实际问题是:如何找到冲突的关联?
这非常非常难。它需要代码来搜索概念模型并查找外键关联中涉及的属性。然后你必须找到它们的值并找到不匹配的地方。很难,但与确定 可能的 冲突何时是 实际的 冲突相比微不足道。让我通过两个例子来阐明这一点。此处,class 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 could not be changed...".
(而且还有更多可能的冲突情况)
因此仅在 OrderId/Order
对中查找不匹配的值是不够的,您还必须检查实体状态并确切知道在哪些状态组合中不允许不匹配。 我的建议:算了,修改你的代码。
虽然有一个肮脏的把戏。当 EF 尝试匹配外键值和引用时,在嵌套 if
树的深处某处,它将我们正在讨论的冲突收集到 ObjectStateManager
的一个成员变量中,名为 [=28] =].可以通过一些反射来获取它的值:
#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
是一个内部 class,因此您只能在调试器中检查集合以了解冲突实体。仅供诊断之用!!!
我在使用 EF 更新期间遇到以下错误:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
是否有任何一般方法来查找导致上述错误的外键属性?
[更新]
对于以下代码导致上述错误的一个案例(我在断开连接的环境中工作,所以我使用 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();
}
在 Entity Framework 中,您可以使用 外键关联 。也就是说,另一个对象的外键表示为一对两个属性:原始外键属性(例如NominalRouting.OrderItemId
)和对象引用(NominalRouting.OrderItem
)。
这意味着您可以设置原始值或对象引用来建立外键关联。如果您设置其中之一,EF 会尽可能使另一个保持同步。不幸的是,这也可能导致原始外键值与其附带的引用之间发生冲突。
很难说出您的情况到底发生了什么。但是,我 do 知道你的 "copying" 对象从一个父对象到另一个父对象的方法是......不理想。首先,更改主键值绝不是一个好主意。通过将它们设置为 0
您可以使对象看起来像新的,但它们不是。其次,您多次将相同的子对象分配给其他父对象。我 认为 因此,你最终会得到大量具有外键 value 而不是 的对象参考.
我说的是"copying",因为这似乎是您想要达到的目标。如果是这样,您应该正确克隆对象并将它们 Add
到每个 targetOrderItem
。同时,我想知道你为什么(显然)克隆所有这些对象。看起来多对多关联在这里更合适。但那是另一回事了。
现在您的实际问题是:如何找到冲突的关联?
这非常非常难。它需要代码来搜索概念模型并查找外键关联中涉及的属性。然后你必须找到它们的值并找到不匹配的地方。很难,但与确定 可能的 冲突何时是 实际的 冲突相比微不足道。让我通过两个例子来阐明这一点。此处,class 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 could not be changed...".
(而且还有更多可能的冲突情况)
因此仅在 OrderId/Order
对中查找不匹配的值是不够的,您还必须检查实体状态并确切知道在哪些状态组合中不允许不匹配。 我的建议:算了,修改你的代码。
虽然有一个肮脏的把戏。当 EF 尝试匹配外键值和引用时,在嵌套 if
树的深处某处,它将我们正在讨论的冲突收集到 ObjectStateManager
的一个成员变量中,名为 [=28] =].可以通过一些反射来获取它的值:
#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
是一个内部 class,因此您只能在调试器中检查集合以了解冲突实体。仅供诊断之用!!!