asp.net-mvc 在 MVC 创建视图上保存多对多关系数据

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11531769/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 02:06:47  来源:igfitidea点击:

Saving Many to Many relationship data on MVC Create view

asp.net-mvcasp.net-mvc-3entity-frameworkrazormany-to-many

提问by PnP

I have some issues with a Many-to-many relationship saving the results of a create view.

我在保存创建视图结果的多对多关系方面遇到了一些问题。

I want to do a create page for a new user profile that has a checklist that lets them choose courses (many to many relationship). My view takes the records from the Coursesdatabase and shows them all with checkboxes.

我想为一个新的用户配置文件创建一个页面,该页面有一个清单,让他们选择课程(多对多关系)。我的视图从Courses数据库中获取记录并用复选框显示它们。

Once the user posts the data, I want to update my userprofilemodel, and also the coursesmany-to-many relationship. That's the code that I have missing!

用户发布数据后,我想更新我的userprofile模型以及courses多对多关系。这就是我缺少的代码!

I'm new at MVC and I've been researching but I couldn't do it yet.

我是 MVC 的新手,我一直在研究,但我还做不到。

I am following this example: http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/updating-related-data-with-the-entity-framework-in-an-asp-net-mvc-application

我正在关注这个例子:http: //www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/updating-related-data-with-the-entity-framework-in-an- asp-net-mvc-应用程序

This is the model:

这是模型:

public class UserProfile
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Courses>  usercourses { get; set; }
}

public class Courses
{
    public int CourseID { get; set; }
    public string CourseDescripcion { get; set; }
    public virtual ICollection<UserProfile> UserProfiles { get; set; }
}

public class UserProfileDBContext : DbContext
{
    public DbSet<UserProfile> UserProfiles { get; set; }
    public DbSet<Courses> usrCourses{ get; set; }
}

I also added a ViewModel:

我还添加了一个 ViewModel:

namespace Mysolution.ViewModels
{
    public class AssignedCourseData
    {
        public int CourseID { get; set; }
        public string CourseDescription { get; set; }
        public bool Assigned { get; set; }
    }
}

This is the createController that populates the courses checkboxes:

这是create填充课程复选框的控制器:

public ActionResult Create()
{
    PopulateCoursesData();
    return View();
}

private void PopulateCoursesData()
{
    var CoursesData = db.usrCourses;
    var viewModel = new List<AssignedCourseData>();
    foreach (var item in CoursesData)
    {
        viewModel.Add(new AssignedCourseData {
            CourseID = item.CourseID,
            CourseDescription  = item.CourseDescription,
            Assigned = false });
    }
    ViewBag.CoursePopulate = viewModel;
}

This is the view

这是视图

@{
    int cnt = 0;
    List<MySolution.ViewModels.AssignedCourseData> courses = ViewBag.CoursePopulate;

    foreach (var course in courses)
    {
        <input type="checkbox" name="selectedCourse" value="@course.CourseID" /> 
        @course.CourseDescription
    }
}

And this is the controler that gets the data (and where I want to save it). It gets as a parameter string[] selectedCoursefor the checkboxes:

这是获取数据的控制器(以及我想保存数据的位置)。它作为string[] selectedCourse复选框的参数:

[HttpPost]
public ActionResult Create(UserProfile userprofile, string[] selectedCourse)
{
    if (ModelState.IsValid)
    {
        db.UserProfiles.Add(userprofile);

        //TO DO: Save data from many to many (this is what I can't do!)

        db.SaveChanges();
    }

    return View(userprofile);
}

回答by Ciaran Bruen

Edit: I've written this up in 3 blog posts with code

编辑:我已经在 3 篇带有代码的博客文章中写下了这个

  • part 1sets up the solution and creates a new user
  • part 2adds the courses and saves them with the user profile
  • part 3allows editing and deletion of users and their courses

Github source: https://github.com/cbruen1/mvc4-many-to-many

Github 源码:https: //github.com/cbruen1/mvc4-many-to-many



I think you've strayed from conventions a little bit in some of your naming for example so I've made changes where I saw fit. In my opinion the best way to have the courses posted back as part of the UserProfile is to have them rendered by an Editor Template which I explain further on.

例如,我认为您在某些命名中有点偏离了约定,因此我在我认为合适的地方进行了更改。在我看来,将课程作为 UserProfile 的一部分发回的最佳方法是让它们由我进一步解释的编辑器模板呈现。

Here's how I would implement all this:

这是我将如何实现所有这些:

(Thanks to @Slauma for pointing out a bug when saving new courses).

(感谢@Slauma 在保存新课程时指出了一个错误)。

  • in your model, your "Courses" class is a single entity and would usually be named Course, and a collection of class Course would be named Courses.
  • instead of having AssignedCourseData in the ViewBag use a view model
  • implement this while creating a new user - i.e. have a standard Create view for a Userprofile containing an AssignedCourseData view model which will be posted back with the UserProfileViewModel.
  • 在您的模型中,您的“Courses”类是一个单一实体,通常命名为 Course,类 Course 的集合将命名为 Courses。
  • 而不是在 ViewBag 中有 AssignedCourseData 使用视图模型
  • 在创建新用户时实现这一点 - 即为包含一个 AssignedCourseData 视图模型的 Userprofile 提供一个标准的 Create 视图,该视图模型将与 UserProfileViewModel 一起回发。

Starting from the DB leave the UserProfile collection as is and name the Course collection Courses:

从 DB 开始,保留 UserProfile 集合,并将 Course 集合命名为 Courses:

public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Course> Courses { get; set; }

In the DbContext class override the OnModelCreating method. This is how you map the many to many relationship between UserProfile and Course:

在 DbContext 类中覆盖 OnModelCreating 方法。这是映射 UserProfile 和 Course 之间多对多关系的方式:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<UserProfile>()
        .HasMany(up => up.Courses)
        .WithMany(course => course.UserProfiles)
        .Map(mc =>
        {
            mc.ToTable("T_UserProfile_Course");
            mc.MapLeftKey("UserProfileID");
            mc.MapRightKey("CourseID");
        }
    );

    base.OnModelCreating(modelBuilder);
}

I would also add a mock initializer class in the same namespace that will give you some courses to start with and means you don't have to manually add them every time your model changes:

我还会在同一个命名空间中添加一个模拟初始化器类,它会给你一些课程开始,这意味着你不必在每次模型更改时手动添加它们:

public class MockInitializer : DropCreateDatabaseAlways<MVC4PartialViewsContext>
{
    protected override void Seed(MVC4PartialViewsContext context)
    {
        base.Seed(context);

        var course1 = new Course { CourseID = 1, CourseDescripcion = "Bird Watching" };
        var course2 = new Course { CourseID = 2, CourseDescripcion = "Basket weaving for beginners" };
        var course3 = new Course { CourseID = 3, CourseDescripcion = "Photography 101" };

        context.Courses.Add(course1);
        context.Courses.Add(course2);
        context.Courses.Add(course3);
    }
}

Add this line to Application_Start() Global.asax to kick start it:

将此行添加到 Application_Start() Global.asax 以启动它:

Database.SetInitializer(new MockInitializer());

So here's the model:

所以这是模型:

public class UserProfile
{
    public UserProfile()
    {
        Courses = new List<Course>();
    }
    public int UserProfileID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Course> Courses { get; set; }
}

public class Course
{
    public int CourseID { get; set; }
    public string CourseDescripcion { get; set; }
    public virtual ICollection<UserProfile> UserProfiles { get; set; }
}

Now create 2 new action results in your Controller to create a new user profile:

现在在你的控制器中创建 2 个新的操作结果来创建一个新的用户配置文件:

public ActionResult CreateUserProfile()
{
    var userProfileViewModel = new UserProfileViewModel { Courses = PopulateCourseData() };

    return View(userProfileViewModel);
}

[HttpPost]
public ActionResult CreateUserProfile(UserProfileViewModel userProfileViewModel)
{
    if (ModelState.IsValid)
    {
        var userProfile = new UserProfile { Name = userProfileViewModel.Name };

        AddOrUpdateCourses(userProfile, userProfileViewModel.Courses);
        db.UserProfiles.Add(userProfile);
        db.SaveChanges();

        return RedirectToAction("Index");
    }

    return View(userProfileViewModel);
}

Here's your PopulateCourseData similar to how you had it except don't put in in the ViewBag - it's now a property on the UserProfileViewModel:

这是您的 PopulateCourseData 类似于您拥有它的方式,除了不要放入 ViewBag - 它现在是 UserProfileViewModel 上的一个属性:

private ICollection<AssignedCourseData> PopulateCourseData()
{
    var courses = db.Courses;
    var assignedCourses = new List<AssignedCourseData>();

    foreach (var item in courses)
    {
        assignedCourses.Add(new AssignedCourseData
        {
            CourseID = item.CourseID,
            CourseDescription = item.CourseDescripcion,
            Assigned = false
        });
    }

    return assignedCourses;
}

Create an Editor Template - in your Views\Shared folder create a new folder called EditorTemplates if you don't already have one. Add a new partial view called AssignedCourseData and paste the code below. This is the bit of magic that renders and names all your check boxes correctly - you don't need a for each loop as the Editor template will create all the items passed in a collection:

创建一个编辑器模板 - 在您的 Views\Shared 文件夹中创建一个名为 EditorTemplates 的新文件夹(如果您还没有)。添加一个名为 AssignedCourseData 的新局部视图并粘贴下面的代码。这是正确呈现和命名所有复选框的魔法 - 您不需要 for 每个循环,因为编辑器模板将创建在集合中传递的所有项目:

@model AssignedCourseData
@using MySolution.ViewModels

<fieldset>
    @Html.HiddenFor(model => model.CourseID)    
    @Html.CheckBoxFor(model => model.Assigned)
    @Html.DisplayFor(model => model.CourseDescription)
</fieldset>

Create a user profile view model in your view models folder - this has a collection of AssignedCourseData objects:

在您的视图模型文件夹中创建一个用户配置文件视图模型 - 这有一组 AssignedCourseData 对象:

public class UserProfileViewModel
{
    public int UserProfileID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<AssignedCourseData> Courses { get; set; }
}

Add a new view called CreateUserprofile.cshtml to create a user profile - you can right click in the already added CreateUserProfile controller method and select "Add View":

添加名为 CreateUserprofile.cshtml 的新视图以创建用户配置文件 - 您可以右键单击已添加的 CreateUserProfile 控制器方法并选择“添加视图”:

@model UserProfileViewModel
@using MySolution.ViewModels

@using (Html.BeginForm("CreateUserProfile", "Course", FormMethod.Post))
{
    @Html.ValidationSummary(true)

    <fieldset>

        @Html.DisplayFor(model => model.Name)
        @Html.EditorFor(model => model.Name)

        // Render the check boxes using the Editor Template
        @Html.EditorFor(x => x.Courses)

    </fieldset>

    <p>
        <input type="submit" value="Create" />
    </p>    
}

This will render the field names correctly in order that they are part of the user profile view model when the form is posted back to the Controller. The fields will be named as such:

这将正确呈现字段名称,以便在表单回发到控制器时它们是用户配置文件视图模型的一部分。这些字段将被命名为:

<fieldset>
    <input data-val="true" data-val-number="The field CourseID must be a number." data-val-required="The CourseID field is required." id="Courses_0__CourseID" name="Courses[0].CourseID" type="hidden" value="1" />    
    <input data-val="true" data-val-required="The Assigned field is required." id="Courses_0__Assigned" name="Courses[0].Assigned" type="checkbox" value="true" /><input name="Courses[0].Assigned" type="hidden" value="false" />
    Bird Watching 
</fieldset>

The other fields will be named similarly except will be indexed with 1 and 2 respectively. Finally here's how to save the courses to the new user profile when the form is posted back. Add this method to your Controller - this is called from the CreateUserProfile action result when the form is posted back:

除了将分别用 1 和 2 索引之外,其他字段的名称将类似。最后,这里介绍了如何在回发表单时将课程保存到新用户配置文件中。将此方法添加到您的控制器 - 在回发表单时从 CreateUserProfile 操作结果调用此方法:

private void AddOrUpdateCourses(UserProfile userProfile, IEnumerable<AssignedCourseData> assignedCourses)
{
    foreach (var assignedCourse in assignedCourses)
    {
        if (assignedCourse.Assigned)
        {
            var course = new Course { CourseID = assignedCourse.CourseID }; 
            db.Courses.Attach(course); 
            userProfile.Courses.Add(course); 
        }
    }
}

Once the courses are part of the user profile EF takes care of the associations. It will add a record for each course selected to the T_UserProfile_Course table created in OnModelCreating. Here's the CreateUserProfile action result method showing the courses posted back :

一旦课程成为用户配置文件的一部分,EF 就会处理关联。它将为在 OnModelCreating 中创建的 T_UserProfile_Course 表中选择的每个课程添加一条记录。这是显示回发的课程的 CreateUserProfile 操作结果方法:

Courses check boxes posted back to the controller

课程复选框回发到控制器

I selected 2 courses and you can see that the courses have been added to the new user profile object:

我选择了 2 门课程,您可以看到这些课程已添加到新的用户配置文件对象中:

Courses added to new userprofile object

添加到新用户配置文件对象的课程