skip to Main Content

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.

2

Answers


  1. I’m not sure what _unitOfWork is in your question. EF provides the Unit of Work pattern so there is usually no need to write your own on top of it.

    Poll should contain the PollOptions collection in your model.

    public class Poll
    {
        [Key]
        public int Id { get; set; }
    
        [Required]
        public string Question { get; set; } = "";
    
        public virtual IEnumerable<PollOption> PollOptions {get; set;}
    }
    

    Then you would just add the pollOptions to the Poll and add that to your datacontext. EF will take care of the keys for you. Slightly simplistic example:

    poll.PollOptions = pollOptions;
    _context.Polls.Add(poll);
    _context.SaveChanges();
    

    It would be better to change the structure of your vm while you are at it.
    It’s generally better not to use domain objects in your VM, in order to maintain separation of layers, and to map one to the other. AutoMapper is useful for mapping between layers without writing a load of boilerplate. If you map PollViewModel to Poll then you wouldn’t need to manually set the pollOptions collection as I have in the example above.

    Login or Signup to reply.
  2. 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

    AddRange(IEnumrable<PollOption> polOptions);

    and change your action method as follows:

    [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);
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search