Ответ 1
Изменить. Я написал это в 3 сообщениях в блоге с кодом
- часть 1 устанавливает решение и создает нового пользователя
- часть 2 добавляет курсы и сохраняет их с профилем пользователя
- часть 3 позволяет редактировать и удалять пользователей и их курсы
Источник Github: https://github.com/cbruen1/mvc4-many-to-many
Я думаю, что вы немного отклонились от условностей в некоторых своих именах, поэтому я сделал изменения там, где я счел нужным. На мой взгляд, лучший способ, чтобы курсы, отправленные назад как часть UserProfile, состоял в том, чтобы их отобразить с помощью шаблона редактора, который я объясню дальше.
Вот как я мог бы реализовать все это:
(Спасибо @Slauma за указание на ошибку при сохранении новых курсов).
- в вашей модели ваш класс "Курсы" - это единое целое и обычно называется "Курс", а набор классов Course будет называться "Курсы".
- вместо того, чтобы иметь AssignedCourseData в ViewBag использовать модель представления
- реализовать это при создании нового пользователя - т.е. иметь стандартное представление Create для пользовательского файла, содержащего модель представления AssignedCourseData, которая будет отправлена обратно с помощью UserProfileViewModel.
Начиная с БД оставляем коллекцию UserProfile как есть и называем курсы коллекции курсов:
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Course> Courses { get; set; }
В классе DbContext переопределить метод OnModelCreating. Вот как вы сопоставляете отношения многих и многих между UserProfile и курсом:
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);
}
Я бы также добавил класс инициализатора mock в том же пространстве имен, который даст вам несколько курсов для начала и означает, что вам не нужно вручную добавлять их каждый раз, когда изменяется ваша модель:
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;
}
Создайте шаблон редактора - в папке Views\Shared создайте новую папку с именем EditorTemplates, если ее еще нет. Добавьте новый частичный вид под названием AssignedCourseData и вставьте код ниже. Это бит магии, который отображает и называет все ваши флажки правильно - для каждого цикла вам не нужен, поскольку шаблон редактора будет создавать все элементы, переданные в коллекции:
@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 заботится об ассоциациях. Он добавит запись для каждого курса, выбранного в таблицу T_UserProfile_Course, созданную в OnModelCreating. Здесь метод результата действия CreateUserProfile показывает курсы, отправленные назад:
Я выбрал 2 курса, и вы можете видеть, что курсы добавлены в новый объект профиля пользователя: