Подтвердить что ты не робот

Связь не может быть изменена, поскольку одно или несколько свойств внешнего ключа не имеют значения NULL

Я получаю следующую ошибку при обновлении с помощью EF:

Операция завершилась неудачно: отношение не может быть изменено, поскольку одно или несколько свойств внешнего ключа не имеют значения NULL. Когда происходит изменение отношения, соответствующее свойство внешнего ключа устанавливается равным нулевому значению. Если внешний ключ не поддерживает нулевые значения, необходимо определить новое отношение, для свойства внешнего ключа должно быть назначено другое ненулевое значение, или не связанный с ним объект должен быть удален.

Существует ли общий способ определения того, какие свойства внешнего ключа вызывают ошибку выше?

[Обновление]

В одном случае следующий код вызывает ошибку выше (я работал в отключенной среде, поэтому я использовал 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();
}
4b9b3361

Ответ 1

В Entity Framework вы можете работать с ассоциациями внешних ключей. То есть внешний ключ к другому объекту выражается как пара двух свойств: свойство примитивного внешнего ключа (например, 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 выдает исключение "Связь не может быть изменена...".

(и возможны более конфликтные ситуации)

Таким образом, вам не достаточно искать непревзойденные значения в парах 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 является внутренним классом, поэтому вы можете только проверять коллекцию в отладчике, чтобы получить представление о конфликтующих объектах. Только для диагностики!!!