Подтвердить что ты не робот

Сохранение данных многих из многих отношений в MVC Создание представления

У меня есть некоторые проблемы с отношениями "многие ко многим", сохраняющие результаты создания.

Я хочу создать страницу создания нового профиля пользователя, в котором есть контрольный список, который позволяет им выбирать курсы (отношения многих или многих). Мое представление берет записи из базы данных Courses и показывает их все с помощью флажков.

Как только пользователь отправляет данные, я хочу обновить мою модель userprofile, а также отношения Courses many-to-many. Это код, который у меня отсутствует!

Я новичок в 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);
}
4b9b3361

Ответ 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 показывает курсы, отправленные назад:

Courses check boxes posted back to the controller

Я выбрал 2 курса, и вы можете видеть, что курсы добавлены в новый объект профиля пользователя:

Courses added to new userprofile object