Ядро EF — Почему объекты сохраняются как новые объекты БД, когда они должны быть связаны с внешним ключом?

#c# #entity-framework-core #ef-fluent-api

#c# #entity-framework-core #ef-fluent-api

Вопрос:

Отказываясь от моделей EF6 DB first для наших контекстов — я создаю ASP Core API, который использует EF Core Code First model для контекстов базы данных. Чтобы сделать это в непрерывном обновлении, мои контексты должны существовать с текущей схемой БД, поэтому я использую EF Fluent API для построения моих сопоставлений объектов для моделей Code First, которые не отражают текущую схему базы данных.

Я сталкиваюсь с проблемой со свойствами навигации по объектам во время операций вставки. Получение данных из БД для основного объекта отлично работает с использованием стандарта.Включить формат (x => x.OtherEntity), сохранение первичной сущности обратно в БД — вот в чем проблема.

Использование Fluent API для сопоставления объектов является новым для меня, поэтому, вероятно, моя проблема заключается в кривой обучения. Я пытался работать с OwnsOne против hasOne но документация MS предполагает, что hasOne () является правильным методом выполнения этого сопоставления.

Мой основной объект, о котором идет речь, имеет теневые свойства для полей внешнего ключа, с которыми связан ключ при использовании.hasOne() в fluent mapping (в коде ниже)

Сообщения об исключениях в этом случае бесполезны, поскольку они не отражают проблему сопоставления, они предполагают, что данные не могут быть вставлены, когда таблицы свойств навигации имеют столбцы идентификаторов (т.Е. не удается вставить запись с явным идентификатором) -> Это странно, потому что я не пытаюсь вставить данные через эти свойства навигации, я просто пытаюсь связать мою основную сущность с этой вторичной сущностью с помощью внешнего ключа.

Сопоставление объектов:

  // WorkOrder Entity Mapping:
 modelBuilder.Entity<WorkOrder>().ToTable("WorkOrder");
 modelBuilder.Entity<WorkOrder>().Property(x => x.Id).HasColumnName("IDWorkOrder");
  modelBuilder.Entity<WorkOrder>().Property(x => x.CreatedBy).HasColumnName("IDUserCreated");
  modelBuilder.Entity<WorkOrder>().Property(x => x.UpdatedBy).HasColumnName("IDUserUpdated");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDWOCategory");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDProblem");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDWOCostCenter");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDWOLocation");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDWOPriority");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDWOStatus");
  modelBuilder.Entity<WorkOrder>().Property<int?>("IDWOTrade");
  modelBuilder.Entity<WorkOrder>().Property<Guid?>("IDUserCompleted");
  modelBuilder.Entity<WorkOrder>().Property<Guid?>("IDParentWO");

  // WO Navigation Properties:
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Category).WithOne().HasForeignKey<WorkOrder>("IDWOCategory").HasPrincipalKey<Category>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Problem).WithOne().HasForeignKey<WorkOrder>("IDProblem").HasPrincipalKey<Problem>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.CostCenter).WithOne().HasForeignKey<WorkOrder>("IDWOCostCenter").HasPrincipalKey<CostCenter>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Location).WithOne().HasForeignKey<WorkOrder>("IDWOLocation").HasPrincipalKey<Location>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Priority).WithOne().HasForeignKey<WorkOrder>("IDWOPriority").HasPrincipalKey<Priority>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Status).WithOne().HasForeignKey<WorkOrder>("IDWOStatus").HasPrincipalKey<Status>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Trade).WithOne().HasForeignKey<WorkOrder>("IDWOTrade").HasPrincipalKey<Trade>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.Requester).WithOne().HasForeignKey<WorkOrder>("IDRequester").HasPrincipalKey<Requester>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.UserCompleted).WithOne().HasForeignKey<WorkOrder>("IDUserCompleted").HasPrincipalKey<User>(c => c.Id);
  modelBuilder.Entity<WorkOrder>().HasOne(x => x.ParentWorkOrder).WithOne().HasForeignKey<WorkOrder>("IDParentWO").HasPrincipalKey<WorkOrder>(c => c.Id);
  

Получение данных в контроллере: (Работает как шарм!)

 [HttpGet("{Id}")]
public async Task<ActionResult<List<WorkOrder>>> GetWorkOrders(Guid Id)
{
    var result = await WorkOrdersContext.WorkOrders
        .Include(x => x.Problem)
        .Include(x => x.Status)
        .Include(x => x.Requester)
        .Include(x => x.ParentWorkOrder)
        .Include(x => x.Category)
        .Include(x => x.Trade)
        .Include(x => x.Location)
        .Include(x => x.CostCenter)
        .Include(x => x.Priority)
        .Where(x => x.Id == Id)
        .ToListAsync();

    return Ok(result);
}
  

Сохранение новых данных в контроллере: (где это прерывается!)

 [HttpPost]
public async Task<ActionResult<WorkOrder>> CreateWorkOrderFromPending([FromBody]WorkOrder call)
{
    // Insert the Work Order to the DB:
    DbContext.WorkOrders.Add(call);
    var saveResult = await DbContext.SaveChangesAsync();

    // Check if any oddities occurred during the save:
    if (saveResult == 0) return BadRequest("An Error occurred during saving and the Call was not saved, please try again.");

    // Return the Inserted Work Order:
    return Ok(call);
}
  

Здесь, когда он пытается сохранить новый порядок выполнения работ, он выдает исключение из-за сопоставленных типов (Проблема, Категория, Центр затрат и т.д. Из фрагмента кода сопоставления объектов) Он пытается сохранить данные как новые объекты, а не строить отношения FK для существующих объектов. Я думаю, что здесь мне не хватает некоторой логики с Fluent API!

Любая помощь была бы очень признательна, поскольку довольно сложно выразить эту проблему в нескольких словах для поиска в Google!

Комментарии:

1. Ничего общего с fluent API. Вы добавляете () редактируете граф объектов в контекст, и все объекты будут помечены как добавленные. Позже вы можете изменить это с помощью средства отслеживания изменений или использовать Attach () вместо Add () , которое установит для объектов неизмененный, если только у них нет ключа, созданного в хранилище null, в этом случае для них будет установлено значение Added.

2. @DavidBrowne-Microsoft, отлично! Спасибо за информацию, я посмотрю, поможет ли это!

3. @DavidBrowne-Microsoft — Я отказываюсь от этого последнего утверждения! Теперь все работает как ожидалось, большое вам спасибо за ввод! Исходя из фона Code First, с которым мне никогда не приходилось этого делать. Attach () и измените состояние объекта, но я думаю, что попытка заставить ядро EF сначала сосуществовать с DB — это тоже не совсем простой или обычный случай! Еще раз спасибо за помощь!

4. Примечание: Все это WithOne выглядит подозрительно и может вызвать у вас проблемы в будущем — маловероятно, что все эти FK будут уникальными внутри WorkOrder таблицы. Подумайте о том, чтобы изменить их на WithMany() .

Ответ №1:

Благодаря @DavidBrowne-Microsoft, у меня есть ответ: попытка заставить материал DB First из нашего прошлого сосуществовать с .NET Core — непростая задача, и исходя из фона Code First, где .Add () был путем, который работал, было трудно понять, как .Attach () собирался сделать свое дело.

 [HttpPost]
public async Task<ActionResult<WorkOrder>> CreateWorkOrderFromPending([FromBody]WorkOrder call)
{
    // Insert the Work Order to the DB:
    // DbContext.WorkOrders.Add(call);
    var entity = DbContext.WorkOrders.Attach(call);

    entity.State = EntityState.Added;

    var saveResult = await DbContext.SaveChangesAsync();

    // Check if any oddities occurred during the save:
    if (saveResult == 0) return BadRequest("An Error occurred during saving and the Call was not saved, please try again.");

    // Return the Inserted Work Order:
    return Ok(call);
}
  

Использование .Attach () вместо .Add() сделал свое дело, поместив объект в и сопоставив FKS с их соответствующими теневыми свойствами из моих Fluent mappings!