I have 2 models, one for Poll and for PollOption. In the create action, I want to add 1 Poll to the database and a list of PollOptions as well. Each PollOption has a foreign key PollId.
When I submit the form, an error occurs because the foreign key is null or 0. I assume this is because the Id of the poll is not ready before the Save function is called thus it is 0 but how can I fix that? The error I get is:
The MERGE statement conflicted with the FOREIGN KEY constraint "FK_PollOptions_Polls_PollId". The conflict occurred in database "favpolls", table "dbo.Polls", column ‘Id’
Models:
public class Poll
{
[Key]
public int Id { get; set; }
[Required]
public string Question { get; set; } = "";
}
public class PollOption
{
[Key]
public int Id { get; set; }
[Required]
public string Option { get; set; } = "";
[Required]
public int PollId { get; set; }
[ForeignKey("PollId")]
[ValidateNever]
public Poll Poll { get; set; }
}
View model:
public class PollVM
{
public Poll Poll { get; set; }
[ValidateNever]
public List<PollOption> PollOptions { get; set; }
}
Action:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(PollVM pollVM)
{
_unitOfWork.Poll.Add(pollVM.Poll);
foreach (var option in pollVM.PollOptions)
{
_unitOfWork.PollOption.Add(option);
}
_unitOfWork.Save();
return View(pollVM);
}
View:
@model PollVM
<div class="index-container">
<div class="subcontainer">
<h1 class="title">Poll Creator</h1>
<h2 class="subtitle">Complete the form below to create your poll!</h2>
<form method="POST" asp-action="Create">
<input asp-for="Poll.Id" type="hidden" />
<div class="form-container">
<div class="form-input">
<label asp-for="Poll.Question">Question:</label>
<input asp-for="Poll.Question" type="text" placeholder="Type the poll question here" required>
</div>
<div class="form-input">
<div class="form-input form-options">
<label>Answer Options:</label>
<div class="form-option">
<input asp-for="PollOptions[0].Id" type="hidden" />
<input asp-for="PollOptions[0].PollId" type="hidden" />
<input asp-for="PollOptions[0].Option" type="text" placeholder="Option 1" required>
<img src="~/images/icons/cross.svg" alt="remove-option" width="30px" class="remove-option" onclick="removeOption(this)">
</div>
<div class="form-option">
<input asp-for="PollOptions[1].Id" type="hidden" />
<input asp-for="PollOptions[1].PollId" type="hidden" />
<input asp-for="PollOptions[1].Option" type="text" placeholder="Option 2" required>
<img src="~/images/icons/cross.svg" alt="remove-option" width="30px" class="remove-option" onclick="removeOption(this)">
</div>
</div>
<button type="button" class="button side-button icon-button">Add Option <img src="~/images/icons/plus.svg" alt="add-option" width="25px"></button>
</div>
<button type="submit" class="button submit-button">Create!</button>
</div>
</form>
</div>
</div>
@section Scripts {
<script src="~/js/answer-options.js" asp-append-version="true"></script>
}
If I remove the poll option "add" functions, the action works correctly and only adds 1 poll which is what I want but I want it to add the poll options as well.
>Solution :
If _unitOfWork.PollOption provides a DbSet that does not need to be changed, but if you use methods that you yourself such as Repositories, you should add the method with the following signature to UnitOfWork
_unitOfWork.PollOption.AddRange(IEnumrable<PollOption> polOptions);
and try this code :
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(PollVM pollVM)
{
pollVM.PollOptions.ForEach(p => p.Poll = pollVM.Poll);
_unitOfWork.PollOption.AddRange(pollVM.PollOptions);
_unitOfWork.Save();
return View(pollVM);
}