Порядок инструкций DELETE и UPDATE в сгенерированном ядром Entity Framework SQL

#c# #postgresql #entity-framework #entity-framework-core

#c# #postgresql #entity-framework #entity-framework-core

Вопрос:

У меня есть следующие объекты, которые существуют как сущности в моем DbContext, подключенные к базе данных PostgreSQL:

 public class Absence
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; set; }

    [Required]
    public AbsenceReason AbsenceReason { get; set; }
}
 

 public class AbsenceReason
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; set; }

    public bool IsDeleted { get; set; }

    public List<Absence> Absences { get; set; }
}
 

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

Это отлично работает, когда я удаляю последнее отсутствие, которое ссылается на мягкую удаленную причину отсутствия. Однако у меня возникают проблемы, когда отсутствие обновляется, чтобы больше не ссылаться на мягкую удаленную причину отсутствия.

С моего контроллера в том же контексте, который я запускаю:

 DbSet.Remove(absenceReason);
DbSet.Update(absence);
await _context.SaveChangesAsync();
 

Не имеет значения, в каком порядке выполняются строки удаления и обновления кода. Я дважды проверил этот факт, и все, что я прочитал, говорит мне, что EF выбирает порядок, в котором инструкции SQL должны выполняться самостоятельно. Проблема в том, что сгенерированный SQL, очевидно, не будет работать:

 DELETE FROM "AbsenceReasons"
WHERE "Id" = @p0;
UPDATE "Absences" SET "AbsenceReasonId" = @p1
WHERE "Id" = @p10;
 

EF решает, что правильно сначала попытаться удалить причину отсутствия, прежде чем обновлять отсутствие, чтобы больше не ссылаться на причину отсутствия. В результате получается, что Microsoft.EntityFrameworkCore.Возникает исключение DbUpdateConcurrencyException.

Как я могу решить эту проблему, сохраняя атомарность?

Примечание: использование EF Core 5.0.1

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

1. Имеет ли значение атомарность для этого? Если нет, почему бы не выполнить обновление, сохранить, удалить, сохранить

2. @CaiusJard Я понимаю, что это сработало бы, но я не хочу жертвовать атомарностью ради чего-то, что должно быть относительно простым в выполнении, на всякий случай, если в будущем возникнет проблема.

3. @NicholasVerstegen SaveChanges сохраняет все ожидающие изменения с помощью внутренней транзакции. Проблема здесь в коде контроллера, а не в том, как ядро EF упорядочивает изменения. Сгенерированный SQL будет работать просто отлично. UPDATE Ничего не изменится. Этот порядок запрашивал код контроллера — a DELETE , за которым следует UPDATE

4. @PanagiotisKanavos Как я указал в вопросе, я проверил это, и порядок в контроллере не имеет значения. Выполнение DbSet.Update(absence); первого и DbSet.Remove(absenceReason); второго дает тот же результат.

Ответ №1:

Я смог выполнить это с помощью TransactionScope, чтобы принудительно установить правильный порядок и сохранить атомарность.

 using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
    DbSet.Update(absence);
    await _context.SaveChangesAsync();
    DbSet.Remove(absenceReason);
    await _context.SaveChangesAsync();
    scope.Complete();
}