【问题标题】:Saving Many to Many relationship data on MVC Create view在 MVC 创建视图上保存多对多关系数据
【发布时间】:2012-07-17 22:31:03
【问题描述】:

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

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

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

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

我正在关注这个例子: 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

这是模型:

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

我还添加了一个 ViewModel:

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

这是填充课程复选框的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;
}

这是风景

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

这是获取数据的控制器(以及我想要保存它的位置)。 它作为复选框的参数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);
}

【问题讨论】:

    标签: asp.net-mvc asp.net-mvc-3 entity-framework razor many-to-many


    【解决方案1】:

    编辑:我已经在 3 篇博客文章中用代码写了这篇文章

    • part 1 设置解决方案并创建新用户
    • part 2 添加课程并与用户个人资料一起保存
    • part 3 允许编辑和删除用户及其课程

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


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

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

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

    • 在你的模型中,你的“Courses”类是一个单一的实体,通常被命名为Course,而Course 类的集合被命名为Courses。
    • 使用视图模型代替 ViewBag 中的 AssignedCourseData
    • 在创建新用户时实现这一点 - 即,为包含将与 UserProfileViewModel 一起回发的 AssignedCourseData 视图模型的用户配置文件有一个标准的创建视图。

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

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

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

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

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

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

    Database.SetInitializer(new MockInitializer());
    

    这是模型:

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

    现在在控制器中创建 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);
    }
    

    这是您的 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;
    }
    

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

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

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

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

    添加一个名为 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>    
    }
    

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

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

    除了分别用 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); 
            }
        }
    }
    

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

    我选择了 2 门课程,您可以看到这些课程已添加到新的用户个人资料对象中:

    【讨论】:

    • +1 很棒的详细答案!唯一缺少的是将课程附加到上下文中,否则 EF 将在数据库中创建重复的课程。在AddOrUpdateCoursesvar course = new Course { ... }; db.Courses.Attach(course); userProfile.Courses.Add(course);
    • 你看得很清楚。后来我确实注意到数据库中有一些错误的课程,但没有时间查看。
    • 这个答案太棒了!...干得好!...非常感谢您的努力!!..
    • @Yash 我见过不同的方法,人们从服务器检索逗号分隔的字符串,拆分它们并使用 js 创建复选框,然后再次将 cs 字符串回发到服务器。像大多数情况一样,有不止一种方法可以做到这一点,但这是 .NET MVC 方式,所以我想这是你最熟悉的问题。
    • @thomvlau - 您似乎缺少 AssignedCategory 类的编辑器模板。
    猜你喜欢
    • 2016-09-10
    • 1970-01-01
    • 2018-07-11
    • 1970-01-01
    • 2016-02-02
    • 1970-01-01
    • 2019-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多