Почему я получаю DbUpdateException: OptimisticConcurrencyException? - PullRequest
0 голосов
/ 06 ноября 2018

У меня есть категория Категория:

public class Category
{
    public int CategoryId { get; set; }
    public string CategoryName { get; set; }
}

У меня также есть класс подкатегории:

public class Subcategory
{
    public int SubcategoryId { get; set; }
    public Category Category { get; set; }
    public string SubcategoryName { get; set; }
}

А класс Аромата:

public class Flavor
{
    public int FlavorId { get; set; }
    public Subcategory Subcategory { get; set; }
    public string FlavorName { get; set; }
}

Тогда у меня также есть классы Filling и Frosting, такие же, как класс Flavor, которые также имеют свойства навигации по категориям и подкатегориям.

У меня есть класс Product, у которого есть свойство навигации Flavor.

Класс OrderItem представляет каждую строку в заказе:

public class OrderItem
{
    public int OrderItemId { get; set; }
    public string OrderNo { get; set; }
    public Product Product { get; set; }
    public Frosting Frosting { get; set; }
    public Filling Filling { get; set; }
    public int Quantity { get; set; }
}

У меня возникают проблемы при попытке сохранить объект OrderItem. Я продолжаю получать DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. с Внутренним Исключением, равным OptimisticConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded.. Я прошелся по коду несколько раз и не могу найти ничего, что изменяет или удаляет какие-либо объекты, загруженные из базы данных. Мне удалось сохранить OrderItem, но он создает повторяющиеся записи элементов Product, Flavor, Subcategory и Category в БД. Я изменил EntityState объекта OrderItem на Modified, но это исключение выше. Я подумал, что это может быть тот факт, что у меня все объекты Product, Frosting и Filling ссылаются на одни и те же объекты Subcategory и Category, поэтому я попытался отсоединить Frosting and Filling, сохранить, присоединить, изменить состояние объекта OrderItem на Modified и снова сохранить, но это также выдает вышеупомянутое исключение.

Следующий оператор создает дубликаты в базе данных:

db.OrderItems.Add(orderItem);

Добавление любого из следующих операторов после строки выше приводит к тому, что db.SaveChanges(); вызывает указанное исключение (как измененное, так и отключенное состояние):

db.Entry(item).State = EntityState.Modified;
db.Entry(item.Product.Flavor.Subcategory.Category).State = EntityState.Modified;
db.Entry(item.Product.Flavor.Subcategory).State = EntityState.Modified;
db.Entry(item.Product.Flavor).State = EntityState.Modified;
db.Entry(item.Product).State = EntityState.Modified;

Может кто-нибудь дать мне некоторое представление? Мои классы плохо разработаны?

1 Ответ

0 голосов
/ 07 ноября 2018

Первым делом нужно проверить, как соотносятся отношения сущностей. Как правило, свойства навигации должны быть помечены как виртуальные, чтобы EF мог использовать их в качестве прокси. Еще одна оптимизация заключается в том, что если объекты ссылаются на подкатегорию, то, поскольку подкаталоги ссылаются на категорию, этим объектам не нужны оба. Вам понадобятся оба, если подкатегории не являются обязательными. Наличие обоих не обязательно вызовет проблемы, но это может привести к сценариям, в которых категория замораживания не соответствует категории подкатегории замораживания. (Видел более, чем достаточно ошибок, подобных этому, в зависимости от того, замерз ли код.

Деталь ошибки, похоже, указывает на то, что EF знает о сущностях, но не рассказывает об их отношениях. Вы должны убедиться, что у вас есть картографические данные, чтобы сообщить EF о взаимосвязи между Frosting и SubCategory. EF может вывести некоторые из них автоматически, но мое предпочтение всегда должно быть явным. (Я ненавижу сюрпризы!)

public class FrostingConfiguration : EntityTypeConfiguration<Frosting>
{
  public FlavorConfiguration()
  {
    ToTable("Flavors");
    HasKey(x => x.FlavorId)
      .Property(x => x.FlavorId)
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

    HasRequired(x => x.SubCategory)
      .WithMany()
      .Map(x => x.MapKey("SubCategoryId");
  }
}

Поскольку у вашей сущности Flavor, по-видимому, нет свойства для SubCategoryId, это помогает сообщить EF об этом. EF, возможно, сможет сделать это, но с идентификаторами и автоматическими соглашениями об именах, которые он ищет, я не пытаюсь вспомнить, что работает автоматически.

Теперь, если это EF Core, вы можете заменить оператор .Map() на:

.ForeignKey("SubCategoryId");

, который установит свойство тени для FK.

Если SubCats не обязательны, замените HasRequired на HasOptional. WithMany() просто означает, что, хотя Flavor ссылается на подкатегорию, SubCategory не поддерживает список вариантов.

Следующим предостережением является передача сущностей за пределы области DBContext, в которую они были загружены. Хотя EF поддерживает отсоединение сущностей от одного контекста и повторное присоединение их к другому, я бы сказал, что эта практика почти всегда гораздо больше проблем, чем она того стоит. Преобразование сущностей в POCO ViewModels / DTO, а затем их загрузка по требованию снова при выполнении обновлений проще и менее подвержено ошибкам, чем попытка их присоединения. Состояние данных, возможно, изменилось между моментом их первоначальной загрузки и повторным присоединением, поэтому отказоустойчивый код должен обрабатывать этот сценарий в любом случае. Это также избавляет от необходимости возиться с измененным состоянием в наборах сущностей. Хотя может показаться эффективным не загружать объекты во второй раз, принимая модели представлений, вы можете оптимизировать чтение намного эффективнее, только извлекая и транспортируя значимые данные, а не целые графы объектов. (Системы обычно читают гораздо больше, чем обновляют). Даже для операций с интенсивным обновлением вы можете использовать ограниченный контекст для представления больших таблиц в виде небольших простых объектов, чтобы более эффективно загружать и обновлять несколько ключевых полей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...