Вопрос или проблема
Я работаю над проектом, и есть тренер, за которым я слежу. Когда я пришел к процессу обновления, я начал сталкиваться с ошибкой, когда я использовал точки останова, я понял, что значение 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);
}
}
Вот визуализация схемы моей базы данных
Проблема, которую я обнаружил, когда продолжал с точкой останова
Ошибка, которую я получаю, выглядит следующим образом
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, Action
1 wrapCloseInAction)
в Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
Ответ или решение
Ошибка, с которой вы столкнулись, имеет отношение к конфликту ограничений внешнего ключа при использовании оператора MERGE в Entity Framework. Конструкции данных вашего приложения, описанные в классе Questions
и Options
, предполагают, что вопрос (Questions) зависит от соответствующего теста (Quiz) через свойство QuizId
, а опции (Options) зависят от вопросов через QuestionId
.
Причины возникновения ошибки
-
Неверное значение QuizId: Анализируя ваш код, можно заметить, что вы создаете новые экземпляры вопросов и опций, однако значение
QuizId
не присваивается. Это и вызывает ситуацию, при которой при добавлении опций к вопросу возникает конфликт внешнего ключа. -
Состояние сущностей: Когда вы обновляете сущность
dbQuiz
, у вас возникают проблемы с маппингом вопросов и опций, которые должны быть связаны с конкретным тестом. ЕслиQuizId
не установлено, БД не сможет установить связь между вопросами и тестами, что ведет к ошибке. -
Ошибки в назначении идентификаторов: При назначении уникальных идентификаторов для новых вопросов и опций, вам необходимо следить за тем, чтобы внешние ключи также корректно указывали на действительные идентификаторы в вашей базе данных.
Рекомендации по исправлению ошибки
-
Корректировка присвоения 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();
-
Обработка обновлений существующих вопросов: Если вы обновляете тест, вам необходимо убедиться, что все существующие вопросы и опции корректно сопоставлены. Проверьте, обновляются ли идентификаторы для уже сохраненных вопросов, и если это не так, вам нужно избегать заменять существующие вопросы новыми:
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); } }
-
Контроль состояния сущностей: Убедитесь, что все связи правильно отслеживаются Entity Framework. Используйте
DbContext.Entry()
для явного отслеживания состояния сущностей, если это необходимо, и контролируйте какAdded
, так иModified
состояния в зависимости от статуса данных.
Заключение
Выступая перед проблемой, связанной с нарушением ограничения внешнего ключа в вашей базе данных при использовании Entity Framework, важно уделить внимание правильному назначению и обновлению идентификаторов для сущностей. Соблюдение этих рекомендаций обеспечит правильную работу вашего приложения и устранит ошибки, возникающие вследствие конфликта с ограничениями.
Should you have any further queries or require additional clarification, feel free to reach out!