MVC 4 - Many-to-Many relation and checkboxes

后端 未结 3 1575
梦谈多话
梦谈多话 2020-12-03 09:05

I\'m working with ASP.NET MVC 4 and Entity Framework. In my database, I have a table Subscription which represents a subscription to public transports. This

相关标签:
3条回答
  • 2020-12-03 09:17

    You start with two view models. The first one which represents a selected company...

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

    ...and the second one for the subscription to create:

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

    Then in the SubscriptionControllers GET action you load the companies from the database to initialize the view model:

    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);
    }
    

    Now, you have a strongly typed view for this action:

    @model SubscriptionCreateViewModel
    
    @using (Html.BeginForm()) {
    
        @Html.EditorFor(model => model.Amount)
    
        @Html.EditorFor(model => model.Companies)
    
        <input type="submit" value="Create" />
        @Html.ActionLink("Cancel", "Index")
    }
    

    To get the company checkboxes rendered correctly you introduce an editor template. It must have the name CompanySelectViewModel.cshtml and goes into the folder Views/Subscription/EditorTemplates (create such a folder manually if it doesn't exist). It's a strongly typed partial view:

    @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 is added as hidden field to preserve the name during a POST.

    Obviously you have to style the views a bit more.

    Now, your POST action would look like this:

    [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);
    }
    

    Instead of using Attach you can also load the company first with var company = _context.Companies.Find(selectedCompany.CompanyId);. But with Attach you don't need a roundtrip to the database to load the companies to be added to the collection.

    (Edit 2: In this answer is a continuation for the Edit actions and views with the same example model.)

    Edit

    Your model is not really a many-to-many relationship. You have two one-to-many relationships instead. The PublicTransportSubscriptionByCompany entity is not needed - normally. If you have a composite primary key in that table made of Id_PublicTransportSubscription, Id_PublicTransportCompany and remove the id column Id_PublicTransportSubscriptionByCompanyId EF would detect this table schema as a many-to-many relationship and create one collection in each of the entities for subscription and company and it would create no entity for the link table. My code above would apply then.

    If you don't want to change the schema for some reason you must change the POST action like so:

    [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);
    }
    
    0 讨论(0)
  • 2020-12-03 09:20

    I prefer this answer: Saving Many to Many relationship data on MVC Create view If you are doing database first, then just skip to the viewmodel part of section 1.

    0 讨论(0)
  • 2020-12-03 09:20

    Just an extension to Slauma's answer. In my case i had to represent many-to-many like a table between Products and Roles, first column representing Products, the header representing Roles and the table to be filled with checkboxes to select roles for product. To achieve this i have used ViewModel like Slauma described, but added another model containing the last two, like so:

    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, in Controller we need to fill data:

    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
                     };
    

    Next, in 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"/>
    }
    

    As you see, EditorFor is using template for 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>
    

    This template uses another template for Roles:

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

    And voila, we have a table containing first column Products, the header contains Roles and the table is filled with checkboxes. We are posting UserViewModel and you will see that all the data are posted.

    0 讨论(0)
提交回复
热议问题