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();
}
4

1 回答 1

64

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

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

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

我说“复制”,因为这似乎是你试图实现的目标。如果是这样,您应该正确地将对象和它们克隆Add到每个targetOrderItem. 同时,我想知道你为什么(显然)克隆所有这些对象。看起来多对多关联在这里更合适。但这是一个不同的主题。

现在您的实际问题是:如何找到冲突的关联?

这非常非常难。它需要代码来搜索概念模型并找到涉及外键关联的属性。然后你必须找到它们的值并找到不匹配的地方。很难,但与确定可能的冲突何时是实际冲突相比微不足道。让我通过两个例子来澄清这一点。在这里,一个类OrderItem具有一个必需的外键关联,由属性OrderOrderId.

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 引发异常“无法更改关系...”。

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

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

不过有一个肮脏的把戏。当 EF 尝试匹配外键值和引用时,if它会在嵌套 s 树的深处某处将我们正在谈论的冲突收集到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

conceptualNullsis a HashSet<EntityEntry>,EntityEntry是一个内部类,因此您只能在调试器中检查集合以了解冲突实体。仅用于诊断目的!!!

于 2015-09-26T23:43:57.863 回答