MVC 4 - Many-to-Many relation and checkboxes

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

Question

Entity Framework and ASP.NET MVC 4 are the tools I use. I have a table in my database called Subscription that represents a public transportation subscription. It is a Many-to-Many relation between these tables that this subscription can give access to several a public transportation system (thus a subscription could have 1, 2, 3,... firms) (I have an intermediate table between them).

I want to make it possible to create a subscription through a page that has a field for the subscription (Amount) and checkboxes for the firms that are offered. Each checkbox corresponds to an active business (a company stored in my database).

Any ideas on how to accomplish that? I read this, but it wasn't that useful.

Here is my table diagram, edited.

Tables Diagram

1
8
5/23/2017 12:00:29 PM

Accepted Answer

There are initially two perspective models. The first one to do so will represent a certain company...

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

And the second one is used to generate the subscription:

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

Next, in theSubscriptionController To initialize the view model, you use the GET action to load the firms from the database:

public ActionResult Create()
{
    var viewModel = new SubscriptionCreateViewModel
    {
        Companies = _context.Companies
            .Select(c => new CompanySelectViewModel
            {
                CompanyId = c.CompanyId,
                Name = c.Name,
                IsSelected = false
            })
            .ToList()
    };

    return View(viewModel);
}

Your highly typed view for this action is now available:

@model SubscriptionCreateViewModel

@using (Html.BeginForm()) {

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

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

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

You introduce an editor template to ensure that the corporate checkboxes are presented correctly. The name must be present.CompanySelectViewModel.cshtml and is placed in the folder.Views/Subscription/EditorTemplates (If such a folder is not present, create one manually.) It's a partial view with strong typing:

@model CompanySelectViewModel

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

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

Name a hidden field is introduced to protect the name during a POST.

Of course, the views need to be styled a little bit more.

Your POST action would now appear as follows:

[HttpPost]
public ActionResult Create(SubscriptionCreateViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        var subscription = new Subscription
        {
            Amount = viewModel.Amount,
            Companies = new List<Company>()
        };

        foreach (var selectedCompany
            in viewModel.Companies.Where(c => c.IsSelected))
        {
            var company = new Company { CompanyId = selectedCompany.CompanyId };
            _context.Companies.Attach(company);

            subscription.Companies.Add(company);
        }

        _context.Subscriptions.Add(subscription);
        _context.SaveChanges();

        return RedirectToAction("Index");
    }

    return View(viewModel);
}

rather than usingAttach You might also start by supplying the business withvar company = _context.Companies.Find(selectedCompany.CompanyId); . yet withAttach The companies that will be added to the collection can be loaded without making a second journey to the database.

(Zzz-72-Zzz: The continuation of this is in Zzz-76-Zzz.Edit use the same example model for actions and views.)

Edit

It's not actually a many-to-many relationship in your model. Instead, you have two one-to-many relationships. ThePublicTransportSubscriptionByCompany entity is typically not required. If that table contains a composite primary key constructed ofId_PublicTransportSubscription, Id_PublicTransportCompany also eliminate the ID columnId_PublicTransportSubscriptionByCompanyId As a many-to-many relationship, EF would identify this table structure as such and build one collection in each of the entities for subscription and company, but it would not create an entity for the link table. The code I mentioned earlier would then be applicable.

Change the POST action as follows if you decide against changing the schema for some reason:

[HttpPost]
public ActionResult Create(SubscriptionCreateViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        var subscription = new Subscription
        {
            Amount = viewModel.Amount,
            SubscriptionByCompanies = new List<SubscriptionByCompany>()
        };

        foreach (var selectedCompany
            in viewModel.Companies.Where(c => c.IsSelected))
        {
            var company = new Company { CompanyId = selectedCompany.CompanyId };
            _context.Companies.Attach(company);

            var subscriptionByCompany = new SubscriptionByCompany
            {
                Company = company
            };

            subscription.SubscriptionByCompanies.Add(subscriptionByCompany);
        }

        _context.Subscriptions.Add(subscription);
        _context.SaveChanges();

        return RedirectToAction("Index");
    }

    return View(viewModel);
}
19
5/23/2017 12:17:25 PM

Popular Answer

Just a continuation of Slauma's response. In my situation, I had to depict a many-to-many relationship using a table between Products and Roles, with the first column representing Products and the header indicating Roles. The table also needed to be populated with checkboxes to allow users to select specific roles for each Product. In order to accomplish this, I constructed a second model that contained the last two, as Slauma described, and used ViewModel as he did:

public class UserViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IEnumerable<ProductViewModel> Products { get; set; }
}

public class ProductViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IEnumerable<RoleViewModel> Roles { get; set; } 
}
public class RoleViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsSelected { get; set; }
}

Next, data must be entered in the controller:

UserViewModel user = new UserViewModel();
user.Name = "Me";
user.Products = new List<ProductViewModel>
                {
                    new ProductViewModel
                    {
                        Id = 1,
                        Name = "Prod1",
                        Roles = new List<RoleViewModel>
                        {
                            new RoleViewModel
                            {
                                Id = 1,
                                Name = "Role1",
                                IsSelected = false
                            }
                            // add more roles
                        }
                    }
                    // add more products with the same roles as Prod1 has
                 };

Afterward, view:

@model UserViewModel@using (Ajax.BeginForm("Create", "User",
new AjaxOptions
{
    HttpMethod = "POST",
    InsertionMode = InsertionMode.Replace,
    UpdateTargetId = "divContainer"
}))
{
<table>
    <thead>
        <tr>
            <th>
            </th>
            @foreach (RoleViewModel role in Model.Products.First().Roles.ToList())
            {
                <th>
                    @role.Name
                </th>
            }
        </tr>
    </thead>
    <tbody>
        @Html.EditorFor(model => model.Products)
    </tbody>
</table>
<input type="submit" name="Create" value="Create"/>
}

You can see that EditorFor uses a template for its products.

@model Insurance.Admin.Models.ProductViewModel
@Html.HiddenFor(model => model.Id)
<tr>
    <th class="col-md-2 row-header">
        @Model.Name
    </th>
    @Html.EditorFor(model => model.Roles)
</tr>

Another framework for roles is utilized in this one:

@model Insurance.Admin.Models.RoleViewModel
@Html.HiddenFor(model => model.Id)
<td>
    @Html.EditorFor(model => model.IsSelected)
</td>

And there it is—a table with the first column being Products, the heading being Roles, and the table being full with checkboxes. You can see that all the data are posted as we post UserViewModel.



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