MVC 4 - 多对多关系和复选框

时间:2022-10-04 15:04:04

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 subscription can provide access to several public transport companies (so a subscription could have 1, 2, 3, ... companies) then it is a Many-to-Many relation between these tables (I have an intermediate table between them).

我正在使用ASP.NET MVC 4和Entity Framework。在我的数据库中,我有一个表Subscription,表示对公共传输的订阅。这个订阅可以提供对几个公共交通公司的访问(因此订阅可能有1,2,3,......公司),然后这些表之间是多对多关系(我之间有一个中间表)。

I want to allow the creation of a subscription throught a page which will contain a field Amount of the subscription and the available companies by checkboxes. Every checkbox represents an existing company (a company stored in my database).

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

Any idea about how to do that? I've read this ASP.NET MVC Multiple Checkboxes but it was not really helpful.

有关如何做到这一点的任何想法?我已经阅读了这个ASP.NET MVC多重复选框,但它并没有真正帮助。

EDIT : Here is my tables diagram.

编辑:这是我的表格图。

MVC 4  - 多对多关系和复选框

3 个解决方案

#1


19  

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:

然后在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);
}

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:

要正确呈现公司复选框,请引入编辑器模板。它必须具有名称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 is added as hidden field to preserve the name during a POST.

名称将添加为隐藏字段以在POST期间保留名称。

Obviously you have to style the views a bit more.

显然你必须对视图进行更多的设计。

Now, your POST action would look like this:

现在,您的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);
}

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.

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

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

(编辑2:在此答案中是使用相同示例模型编辑操作和视图的延续。)

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.

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

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

如果由于某种原因不想更改架构,则必须更改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);
}

#2


1  

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.

我更喜欢这个答案:在MVC创建视图中保存多对多的关系数据如果您先做数据库,那么只需跳到第1部分的viewmodel部分。

#3


1  

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:

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

Next, in Controller we need to fill data:

接下来,在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
                 };

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:

如您所见,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>

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.

瞧,我们有一个包含第一列产品的表,标题包含角色,表格中填充了复选框。我们发布了UserViewModel,您将看到所有数据都已发布。

#1


19  

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:

然后在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);
}

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:

要正确呈现公司复选框,请引入编辑器模板。它必须具有名称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 is added as hidden field to preserve the name during a POST.

名称将添加为隐藏字段以在POST期间保留名称。

Obviously you have to style the views a bit more.

显然你必须对视图进行更多的设计。

Now, your POST action would look like this:

现在,您的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);
}

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.

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

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

(编辑2:在此答案中是使用相同示例模型编辑操作和视图的延续。)

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.

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

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

如果由于某种原因不想更改架构,则必须更改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);
}

#2


1  

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.

我更喜欢这个答案:在MVC创建视图中保存多对多的关系数据如果您先做数据库,那么只需跳到第1部分的viewmodel部分。

#3


1  

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:

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

Next, in Controller we need to fill data:

接下来,在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
                 };

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:

如您所见,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>

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.

瞧,我们有一个包含第一列产品的表,标题包含角色,表格中填充了复选框。我们发布了UserViewModel,您将看到所有数据都已发布。