编辑:我用代码在 3 篇博客文章中写了这个
Github 来源:
https ://github.com/cbruen1/mvc4-many-to-many
例如,我认为您在某些命名中已经偏离了惯例,所以我在我认为合适的地方进行了更改。在我看来,将课程作为 UserProfile 的一部分发回的最佳方式是让它们由编辑器模板呈现,我将进一步解释。
以下是我将如何实现所有这些:
(感谢@Slauma 在保存新课程时指出了一个错误)。
- 在您的模型中,您的“课程”类是单个实体,通常命名为 Course,而类 Course 的集合将命名为 Courses。
- 而不是在 ViewBag 中使用 AssignedCourseData 使用视图模型
- 在创建新用户时实现这一点 - 即,为包含将与 UserProfileViewModel 一起回发的 AssignedCourseData 视图模型的 Userprofile 有一个标准的 Create 视图。
从 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 门课程,您可以看到这些课程已添加到新的用户配置文件对象中: