【问题标题】:MVC 4 - Many-to-Many relation and checkboxesMVC 4 - 多对多关系和复选框
【发布时间】:2013-04-29 07:23:47
【问题描述】:

我正在使用 ASP.NET MVC 4 和实体框架。在我的数据库中,我有一个表 Subscription 代表对公共交通的订阅。此订阅可以提供对多个 公共交通公司 的访问(因此订阅可以有 1、2、3、... 公司),那么这些表之间是多对多关系(我有它们之间的中间表)。

我想允许通过一个页面创建订阅,该页面将包含一个字段Amount 的订阅和复选框中的可用公司。每个复选框都代表一个现有公司(存储在我的数据库中的公司)。

知道如何做到这一点吗?我已经阅读了这个ASP.NET MVC Multiple Checkboxes,但它并没有真正的帮助。

编辑:这是我的表格图。

【问题讨论】:

  • 公司是“固定”数据集(意味着它们不会在此视图中被修改?)
  • 没错。我的公司存储在我的“公司”表中,不会在此处在创建/编辑视图中进行修改。
  • 你可以有一个这样的下拉列表:link
  • 不,我不是在寻找下拉列表,因为正如我所说,订阅可能由 2 家或更多公司组成。所以这就是为什么我认为复选框更好。
  • 我不知道您使用的是哪个视图引擎,但这并不难,您只需要迭代公司并创建按钮。您可能需要更改模型类。

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


【解决方案1】:

您从两个视图模型开始。第一个代表选定公司的...

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

...以及要创建的第二个订阅:

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

然后在SubscriptionControllers GET 操作中,您从数据库中加载公司以初始化视图模型:

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

现在,您有一个用于此操作的强类型视图:

@model SubscriptionCreateViewModel

@using (Html.BeginForm()) {

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

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

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

要正确呈现公司复选框,您需要引入一个编辑器模板。它必须具有名称CompanySelectViewModel.cshtml 并进入文件夹Views/Subscription/EditorTemplates(如果不存在则手动创建这样的文件夹)。这是一个强类型的局部视图:

@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 被添加为隐藏字段以在 POST 期间保留名称。

显然,您必须对视图进行更多样式设置。

现在,您的 POST 操作将如下所示:

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

您也可以先使用var company = _context.Companies.Find(selectedCompany.CompanyId); 加载公司,而不是使用Attach。但是使用Attach,您无需往返数据库即可加载要添加到集合中的公司。

编辑 2:在 this answer 中是 Edit 操作和视图的延续,具有相同的示例模型。)

编辑

您的模型并不是真正的多对多关系。相反,您有两个一对多的关系。 PublicTransportSubscriptionByCompany 实体通常不需要。如果您在由 Id_PublicTransportSubscription, Id_PublicTransportCompany 组成的表中有一个复合主键并删除了 id 列 Id_PublicTransportSubscriptionByCompanyId EF 会将此表模式检测为多对多关系,并在每个实体中为订阅和公司创建一个集合它不会为链接表创建实体。我上面的代码将适用。

如果您出于某种原因不想更改架构,则必须像这样更改 POST 操作:

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

【讨论】:

  • 非常感谢,我想这会有所帮助!我现在就试试,然后告诉你。但是,有一个小问题:部分视图的渲染将使用您的示例自动完成?再次感谢
  • @Traffy:是的,EditorFor(model =&gt; model.Companies) 检测到 Companies 是一个集合,并为每个项目呈现一个局部视图。模板文件的名称(必须与模型名称匹配)和路径必须准确,因为 MVC 使用某些约定搜索局部视图(通常在 Views/{ControllerName}Shared 等中,但在名为 @987654344 的子文件夹中@)。
  • 好的,谢谢。这里又是一个问题:在 post 方法中,您创建一个新订阅并填充金额和公司。但是,我的表中没有公司列表或属性。我删除它有关系吗?
  • 好的,我知道你想做什么。给我 2 秒时间,让我向您展示我的表格模型。
  • @Traffy:还有一点工作要做。您将拥有类似的视图模型(但包括 SubscriptionEditViewModel 中的 ID),加载包含已相关公司 ID 的订阅,再次加载 all 公司,然后将 IsSelected 设置为 true对于已经与订阅相关的公司。 POST 操作更加困难,因为现在您需要在用户取消选中公司时从链接表中删除实体,并在用户选中新公司时将实体添加到链接表中。当您遇到困难时,只需开始并提出新问题:)
【解决方案2】:

我更喜欢这个答案:Saving Many to Many relationship data on MVC Create view 如果你先做数据库,那么直接跳到第 1 节的 viewmodel 部分。

【讨论】:

    【解决方案3】:

    只是对 Slauma 答案的扩展。在我的情况下,我必须表示多对多,例如产品和角色之间的表格,第一列表示产品,标题表示角色,并且要填充复选框以选择产品角色的表格。 为了实现这一点,我使用了 Slauma 描述的 ViewModel,但添加了另一个包含最后两个的模型,如下所示:

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

    接下来,我们需要在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
                     };
    

    接下来,在视图中:

    @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"/>
    }
    

    如您所见,EditorFor 正在使用产品模板:

    @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>
    

    此模板使用另一个角色模板:

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

    瞧,我们有一个包含第一列 Products 的表,标题包含 Roles,表中填充了复选框。我们正在发布 UserViewModel,您会看到所有数据都已发布。

    【讨论】:

      猜你喜欢
      • 2013-07-22
      • 1970-01-01
      • 1970-01-01
      • 2014-07-22
      • 1970-01-01
      • 1970-01-01
      • 2013-09-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多