Ошибка конфликта оператора MERGE с ограничением внешнего ключа.

Вопрос или проблема

Я работаю над проектом, и есть тренер, за которым я слежу. Когда я пришел к процессу обновления, я начал сталкиваться с ошибкой, когда я использовал точки останова, я понял, что значение QuizId пришло 0000 на этапе сохранения. Когда я проверяю базу данных, все мои соединения верны, значения id также имеются, но у меня есть проблема с выводом.

Questions.cs

[Key]
public Guid Id { get; set; }
public string Text { get; set; }

public Guid QuizId { get; set; }
[ForeignKey(nameof(QuizId))]
public virtual Quiz Quiz { get; set; }

public virtual ICollection<Options> Options { get; set; } = [];

Options.cs

[Key]
public int Id { get; set; }

public string Text { get; set; }
public bool IsCorrect { get; set; }
public Guid QuestionId { get; set; }

[ForeignKey(nameof(QuestionId))]
public virtual Questions Questions { get; set; }

Quiz.cs

  [Key]
  public Guid Id { get; set; }
  public string? Name { get; set; }
  public int TotalQuestions { get; set; }
  public int TimeInMinutes { get; set; }
  public bool IsActive { get; set; }

  public Guid CategoryId { get; set; }
  [ForeignKey(nameof(CategoryId))]
  public virtual Category? Category { get; set; }

  public ICollection<Questions> Questions { get; set; } = [];

Ниже приведен блок кода, который я использовал для сохранения данных

public async Task<QuizApiResponse> SaveQuizAsync(QuizSaveDto dto)
{
    var questions = dto.Question.Select(q => new Questions
    {
        Id = Guid.NewGuid(),
        Text = q.Text,
        Options = q.Option.Select(o => new Options
        {
            Id = 0,
            Text = o.Text,
            IsCorrect = o.IsCorrect
        }).ToArray()
    }).ToArray();

    if (dto.Id == Guid.Empty)
    {
        var quiz = new Quiz
        {
            Id = Guid.NewGuid(),
            Name = dto.Name,
            CategoryId = dto.CategoryId,
            TotalQuestions = dto.TotalQuestions,
            TimeInMinutes = dto.TimeInMinutes,
            IsActive = dto.IsActive,
            Questions = questions
        };
        _context.Quizzes.Add(quiz);
    }
    else
    {
        var dbQuiz = await _context.Quizzes.FirstOrDefaultAsync(q => q.Id == dto.Id);//QuizId имеет
        if (dbQuiz == null)
        {
            return QuizApiResponse.Fail("Викторина не существует");
        }
        dbQuiz.CategoryId = dto.CategoryId;
        dbQuiz.IsActive = dto.IsActive;
        dbQuiz.Name = dto.Name;
        dbQuiz.TimeInMinutes = dto.TimeInMinutes;
        dbQuiz.TotalQuestions = dto.TotalQuestions;
        dbQuiz.Questions = questions;//QuizId равен 0000

        _context.Quizzes.Update(dbQuiz);// здесь QuizId равен null
    }

    try
    {
        await _context.SaveChangesAsync();
        return QuizApiResponse.Success();
    }
    catch (Exception ex)
    {
        return QuizApiResponse.Fail(ex.Message);
    }
}

Вот визуализация схемы моей базы данныхdatabase

Проблема, которую я обнаружил, когда продолжал с точкой останова
enter image description here

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

Microsoft.EntityFrameworkCore.Update[10000]
Произошло исключение в базе данных при сохранении изменений для типа контекста ‘BlazingQuiz.Api.Data.Repositories.QuizContext’.
Microsoft.EntityFrameworkCore.DbUpdateException: Произошла ошибка при сохранении изменений сущности. См. внутреннее исключение для подробностей.
—> Microsoft.Data.SqlClient.SqlException (0x80131904): Оператор MERGE вызвал конфликт с ограничением FOREIGN KEY “FK_Options_Questions_QuestionId”. Конфликт произошел в базе данных “BlazingQuiz”, таблице “dbo.Questions”, столбце ‘Id’.
в Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action1 wrapCloseInAction) в Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action1 wrapCloseInAction)
в Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)

Ответ или решение

Ошибка, с которой вы столкнулись, имеет отношение к конфликту ограничений внешнего ключа при использовании оператора MERGE в Entity Framework. Конструкции данных вашего приложения, описанные в классе Questions и Options, предполагают, что вопрос (Questions) зависит от соответствующего теста (Quiz) через свойство QuizId, а опции (Options) зависят от вопросов через QuestionId.

Причины возникновения ошибки

  1. Неверное значение QuizId: Анализируя ваш код, можно заметить, что вы создаете новые экземпляры вопросов и опций, однако значение QuizId не присваивается. Это и вызывает ситуацию, при которой при добавлении опций к вопросу возникает конфликт внешнего ключа.

  2. Состояние сущностей: Когда вы обновляете сущность dbQuiz, у вас возникают проблемы с маппингом вопросов и опций, которые должны быть связаны с конкретным тестом. Если QuizId не установлено, БД не сможет установить связь между вопросами и тестами, что ведет к ошибке.

  3. Ошибки в назначении идентификаторов: При назначении уникальных идентификаторов для новых вопросов и опций, вам необходимо следить за тем, чтобы внешние ключи также корректно указывали на действительные идентификаторы в вашей базе данных.

Рекомендации по исправлению ошибки

  1. Корректировка присвоения QuizId: Убедитесь, что при создании экземпляра Questions, вы присваиваете соответствующий идентификатор quiz для каждого вопроса. Это можно сделать следующим образом:

    var questions = dto.Question.Select(q => new Questions
    {
        Id = Guid.NewGuid(),
        Text = q.Text,
        QuizId = quiz.Id, // Присваиваем QuizId
        Options = q.Option.Select(o => new Options
        {
            Id = 0, // Вы можете заменить это на Guid.NewGuid() вместо 0, если вам нужны уникальные идентификаторы
            Text = o.Text,
            IsCorrect = o.IsCorrect
        }).ToArray()
    }).ToArray();
  2. Обработка обновлений существующих вопросов: Если вы обновляете тест, вам необходимо убедиться, что все существующие вопросы и опции корректно сопоставлены. Проверьте, обновляются ли идентификаторы для уже сохраненных вопросов, и если это не так, вам нужно избегать заменять существующие вопросы новыми:

    foreach (var question in questions)
    {
        var existingQuestion = await _context.Questions.FindAsync(question.Id);
        if (existingQuestion != null)
        {
            existingQuestion.Text = question.Text;
            existingQuestion.Options = question.Options; // Поскольку вы могли забыть обновить связанные опции
        }
        else
        {
            // Добавьте новый вопрос, если он не найден
            question.QuizId = dbQuiz.Id; 
            _context.Questions.Add(question);
        }
    }
  3. Контроль состояния сущностей: Убедитесь, что все связи правильно отслеживаются Entity Framework. Используйте DbContext.Entry() для явного отслеживания состояния сущностей, если это необходимо, и контролируйте как Added, так и Modified состояния в зависимости от статуса данных.

Заключение

Выступая перед проблемой, связанной с нарушением ограничения внешнего ключа в вашей базе данных при использовании Entity Framework, важно уделить внимание правильному назначению и обновлению идентификаторов для сущностей. Соблюдение этих рекомендаций обеспечит правильную работу вашего приложения и устранит ошибки, возникающие вследствие конфликта с ограничениями.

Should you have any further queries or require additional clarification, feel free to reach out!

Оцените материал
Добавить комментарий

Капча загружается...