MVC 4 Edit Controller/ View Many to Many relation and checkboxes

asp.net-mvc-4 c# checkbox entity-framework many-to-many

Question

I'm working with ASP.NET MVC 4 and Entity Framework and I was searching for some way to make many to many relation and checkboxes from my db for a Create/Edit controller and view, I have found the answer with @Slauma answer for Create in MVC 4 - Many-to-Many relation and checkboxes but, I'd really like to see how this extends to Edit and Delete functionality as well like some other partners in this solution. Could someone please show how I would populate the ClassificationSelectViewModel in the Edit controller method to get both the "checked" and "unchecked" values? this is a Matt Flowers question that will solve mine too.

1
3
5/23/2017 12:26:41 PM

Accepted Answer

The following is a continuation of this answer that describes Create GET and POST actions for a model with many-to-many relationship between entities Subscription and Company. Here is the procedure for the Edit actions how I would do it (except that I probably wouldn't put all the EF code into the controller actions but extract it into extension and service methods):

The CompanySelectViewModel remains unchanged:

public class CompanySelectViewModel
{
    public int CompanyId { get; set; }
    public string Name { get; set; }
    public bool IsSelected { get; set; }
}

The SubscriptionEditViewModel is the SubscriptionCreateViewModel plus the Subscription's key property:

public class SubscriptionEditViewModel
{
    public int Id { get; set; }
    public int Amount { get; set; }
    public IEnumerable<CompanySelectViewModel> Companies { get; set; }
}

The GET action could look like this:

public ActionResult Edit(int id)
{
    // Load the subscription with the requested id from the DB
    // together with its current related companies (only their Ids)
    var data = _context.Subscriptions
        .Where(s => s.SubscriptionId == id)
        .Select(s => new
        {
            ViewModel = new SubscriptionEditViewModel
            {
                Id = s.SubscriptionId
                Amount = s.Amount
            },
            CompanyIds = s.Companies.Select(c => c.CompanyId)
        })
        .SingleOrDefault();

    if (data == null)
        return HttpNotFound();

    // Load all companies from the DB
    data.ViewModel.Companies = _context.Companies
        .Select(c => new CompanySelectViewModel
        {
            CompanyId = c.CompanyId,
            Name = c.Name
        })
        .ToList();

    // Set IsSelected flag: true (= checkbox checked) if the company
    // is already related with the subscription; false, if not
    foreach (var c in data.ViewModel.Companies)
        c.IsSelected = data.CompanyIds.Contains(c.CompanyId);

    return View(data.ViewModel);
}

The Edit view is the Create view plus a hidden field for the Subscription's key property Id:

@model SubscriptionEditViewModel

@using (Html.BeginForm()) {

    @Html.HiddenFor(model => model.Id)
    @Html.EditorFor(model => model.Amount)

    @Html.EditorFor(model => model.Companies)

    <input type="submit" value="Save changes" />
    @Html.ActionLink("Cancel", "Index")
}

The editor template to select a company remains unchanged:

@model CompanySelectViewModel

@Html.HiddenFor(model => model.CompanyId)
@Html.HiddenFor(model => model.Name)

@Html.LabelFor(model => model.IsSelected, Model.Name)
@Html.EditorFor(model => model.IsSelected)

And the POST action could be like this:

[HttpPost]
public ActionResult Edit(SubscriptionEditViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        var subscription = _context.Subscriptions.Include(s => s.Companies)
            .SingleOrDefault(s => s.SubscriptionId == viewModel.Id);

        if (subscription != null)
        {
            // Update scalar properties like "Amount"
            subscription.Amount = viewModel.Amount;
            // or more generic for multiple scalar properties
            // _context.Entry(subscription).CurrentValues.SetValues(viewModel);
            // But this will work only if you use the same key property name
            // in ViewModel and entity

            foreach (var company in viewModel.Companies)
            {
                if (company.IsSelected)
                {
                    if (!subscription.Companies.Any(
                        c => c.CompanyId == company.CompanyId))
                    {
                        // if company is selected but not yet
                        // related in DB, add relationship
                        var addedCompany = new Company
                            { CompanyId = company.CompanyId };
                        _context.Companies.Attach(addedCompany);
                        subscription.Companies.Add(addedCompany);
                    }
                }
                else
                {
                    var removedCompany = subscription.Companies
                       .SingleOrDefault(c => c.CompanyId == company.CompanyId);
                    if (removedCompany != null)
                        // if company is not selected but currently
                        // related in DB, remove relationship
                        subscription.Companies.Remove(removedCompany);
                }
            }

            _context.SaveChanges();
        }

        return RedirectToAction("Index");
    }

    return View(viewModel);
}

The Delete actions are less difficult. In the GET action you could load a few subscription properties to display on the delete confirmation view:

public ActionResult Delete(int id)
{
    // Load subscription with given id from DB
    // and populate a `SubscriptionDeleteViewModel`.
    // It does not need to contain the related companies

    return View(viewModel);
}

And in the POST action you load the entity and delete it then. There is no need to include the companies because in a many-to-many relationship (usually) cascading delete on the link table is enabled so that the database will take care of deleting the link entries together with the parent Subscription:

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirm(int id)
{
    var subscription = _context.Subscriptions.Find(id);
    if (subscription != null)
        _context.Subscriptions.Remove(subscription);

    return RedirectToAction("Index");
}
10
5/23/2017 10:34:04 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow