Много проблем (внешний ключ или строки не были обновлены ошибки) при попытке сначала сохранить сложные объекты в коде EF - PullRequest
0 голосов
/ 29 марта 2012

В моем приложении довольно глубокая иерархия объектов, и у меня возникают проблемы с сохранением объектов.В зависимости от порядка, в котором я что-то делаю, у меня возникает одна из двух ошибок:

[OptimisticConcurrencyException: оператор обновления, вставки или удаления магазина затронул неожиданное количество строк (0).Объекты могут быть изменены или удалены с момента загрузки объектов.Обновите записи ObjectStateManager.]

или

[DbUpdateException: произошла ошибка при сохранении сущностей, которые не предоставляют свойства внешнего ключа для их отношений.Свойство EntityEntries вернет значение NULL, поскольку один объект не может быть определен как источник исключения.Обработка исключений при сохранении может быть упрощена путем предоставления свойств внешнего ключа в типах объектов.Подробности смотрите в InnerException.]

Вот классы, с которыми я работаю:

public class SpecialEquipment : Entity
{
    public Equipment Equipment { get; set; }
    public virtual ICollection<AutoclaveValidation> Validations { get; set; }
}

public class Equipment : Entity
{
    public string Model { get; set; }
    public string SerialNumber { get; set; }
    public Location Location { get; set; }
    public EquipmentType EquipmentType { get; set; }
    public ICollection<Identifier> Identifiers { get; set; }
}

public class Identifier : Entity
{
    public IdentifierType Type { get; set; }
    public string Value { get; set; }
}

public class Location : Entity
{
    public Building Building { get; set; }
    public string Room { get; set; }
}

Я пытался заполнить один SpecialEquipment объект на основе формывходы и уже существующие объекты в базе данных, а затем сохранить специальное оборудование для проталкивания всех изменений, это выглядит так:

Building building = buildingService.GetExistingOrNew(viewModel.BuildingCode)); //checks to see if building exists already, if not, create it, save it, and re-query
Location location = locationService.GetExistingOrNew(viewModel.Room, building); //checks to see if location exists already, if not, create it, save it, and re-query
EquipmentType equipmentType = equipmentTypeService.GetOne(x => x.Name == EquipmentTypeConstants.Names.Special);
Equipment equipment = new Equipment{ EquipmentType = equipmentType, Location = location };
equipment.Identifiers = new Collection<Identifier>();

foreach (FormIdentifier formIdentifier in identifiers)
{
    FormIdentifier fIdentifier = formIdentifier;
    IdentifierType identifierType = identifierTypeService.GetOne(x => x.Id == fIdentifier.Key);

     equipment.Identifiers.Add(new Identifier { Type = identifierType, Value = fIdentifier.Value });

}

EntityServiceFactory.GetService<EquipmentService>().Save(equipment);
SpecialEquipment specialEquipment = new SpecialEquipment();
specialEquipment.Equipment = equipment;

specialEquipmentService.Save(specialEquipment);

Этот код возвращает Store update, insert, or delete statement affected an unexpected number of rows (0).Если я закомментирую foreach identifiers ИЛИ, поставьте foreach identifiers после equipment save и затем вызовите equipment save после цикла, код работает.Если я закомментирую строку foreach identifiers и save equipment, я получу: The INSERT statement conflicted with the FOREIGN KEY constraint "SpeicalEquipment_Equipment". The conflict occurred in database "xxx", table "dbo.Equipments", column 'Id'.

Так как я могу сделать так, чтобы эти ошибки не возникали, но все же сохраняли мой объект?Есть лучший способ сделать это?Также мне не нравится сохранять объект моего оборудования, затем связывать / сохранять мои идентификаторы и / или затем мой объект специального оборудования, потому что в случае возникновения ошибки между этими этапами у меня будут потерянные данные.Кто-то может помочь?

Я должен упомянуть несколько вещей, которые не унаследованы из кода, но были ответы, которые я видел на похожие вопросы:

  • Моя структура хранит контекст вHttpContext, поэтому все сервисные методы, которые я использую в своем API, используют один и тот же контекст в этом блоке кода.Таким образом, все объекты поступают / хранятся в одном контексте.
  • Конструктор My Entity заполняет ID каждый раз, когда создается новый объект, ни у каких объектов нет пустого первичного ключа.

Редактировать:По запросу комментариев:

Мой метод .Save вызывает вставку или обновление в зависимости от того, существует сущность или нет (в этом примере вставка вызывается, поскольку specialEquipment является новым):

 public void Insert(TClass entity)
    {
        if (Context.Entry(entity).State == EntityState.Detached)
        {
            Context.Set<TClass>().Attach(entity);
        }
        Context.Set<TClass>().Add(entity);
        Context.SaveChanges();
    }

    public void Update(TClass entity)
    {
        DbEntityEntry<TClass> oldEntry = Context.Entry(entity);

        if (oldEntry.State == EntityState.Detached)
        {
            Context.Set<TClass>().Attach(oldEntry.Entity);
        }

        oldEntry.CurrentValues.SetValues(entity);
        //oldEntry.State = EntityState.Modified;

        Context.SaveChanges();
    }

GetExistingOrNew для Building и location одинаковы по логике:

 public Location GetExistingOrNew(string room, Building building)
    {
        Location location = GetOne(x => x.Building.Code == building.Code && x.Room == room);
        if(location == null)
        {
            location = new Location {Building = building, Room = room};
            Save(location);
            location = GetOne(x => x.Building.Code == building.Code && x.Room == room);
        }


        return location;
    }

Get просто передает это, где предикат к контексту в моем репозитории с singleOrDefault.Я использую сервисный уровень / уровень репозитория / объектный слой для моей платформы.

Ответы [ 2 ]

4 голосов
/ 30 марта 2012

Ваш Insert метод не является правильным:

public void Insert(TClass entity)
{
    if (Context.Entry(entity).State == EntityState.Detached)
        Context.Set<TClass>().Attach(entity);
    Context.Set<TClass>().Add(entity);
    Context.SaveChanges();
}

specialEquipment - это новый объект, а также связанный specialEquipment.Equipment (вы создаете оба с new)

Посмотрите, что произойдет, если вы передадите specialEquipment в метод Insert:

  • specialEquipment отсоединен, потому что он новый
  • Итак, вы присоединяете его к контексту
  • Attach присоединяет specialEquipment и связанный specialEquipment.Equipment, а также потому, что оба они были отделены от контекста
  • Оба находятся в состоянии Unchanged сейчас.
  • Теперь вы добавляете specialEquipment: это изменяет состояние specialEquipment на Added, но не состояние specialEquipment.Equipment, оно все еще Unchanged.
  • Теперь вы звоните SaveChanges: EF создает INSERT для добавленной сущности specialEquipment. Но поскольку specialEquipment.Equipment находится в состоянии Unchanged, он не вставляет эту сущность, он просто устанавливает внешний ключ в specialEquipment
  • Но это значение FK не существует (потому что specialEquipment.Equipment на самом деле тоже новое)
  • Результат: вы получаете нарушение ограничения FK.

Вы пытаетесь решить проблему с вызовом Save для equipment, но у вас такая же проблема с новым identifiers, который в итоге вызовет исключение.

Я думаю, что ваш код должен работать, если вы добавляете specialEquipment (как корень графа объекта) в конце один раз в контекст - без присоединения его, так что весь граф новых объектов добавляется, в основном просто :

context.Set<SpecialEquipment>().Add(specialEquipment);
context.SaveChanges();

(Кстати: ваш Update также выглядит некорректно, вы просто копируете каждое свойство entity в себя. Контекст не обнаруживает никаких изменений, и SaveChanges не записывает в оператор никаких UPDATE. база данных.)

0 голосов
/ 29 марта 2012

Мое предположение?У него не может быть идентификатора, если вы его не сохранили, и это является корнем проблемы (так как он работает, если вы сохраните сначала).Вставьте все в транзакцию, поэтому, если что-то пойдет не так, все откатывается.Тогда у вас нет сирот.

См. http://msdn.microsoft.com/en-us/library/bb738523.aspx, как использовать транзакции с EF.

...