Вопрос по razor, asp.net-mvc-3, many-to-many, asp.net-mvc, entity-framework – Сохранение данных отношений «многие ко многим» в MVC Create view

33

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

Я хочу создать страницу для нового профиля пользователя с контрольным списком, который позволяет им выбирать курсы (отношения многие ко многим). Мой взгляд берет записи из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- приложение

Это модель:

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

Ваш Ответ

1   ответ
73

EditЯ написал это в 3 постах с кодом

part 1 part 2 adds the courses and saves them with the user profile part 3 allows editing and deletion of users and their courses

Github источник: https://github.com/cbruen1/mvc4-many-to-many

Я думаю, что вы немного отклонились от условностей, например, в некоторых из своих наименований, поэтому я внес изменения, которые я посчитал нужным. На мой взгляд, лучший способ опубликовать курсы как часть UserProfile - это сделать их с помощью шаблона редактора, который я объясню далее.

Вот как я бы все это реализовал:

(Спасибо @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.

Начиная с БД, оставьте коллекцию UserProfile как есть и назовите коллекцию курсов Курсы:

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

Создайте шаблон редактора. В папке Views \ Shared создайте новую папку с именем EditorTemplates, если у вас ее еще нет. Добавьте новое частичное представление с именем AssignedCourseData и вставьте приведенный ниже код. Это волшебство, которое правильно отображает и присваивает названия всем вашим флажкам - вам не нужен каждый цикл, так как шаблон Editor создаст все элементы, переданные в коллекции:

@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 и выбрать & quot; Добавить представление & quot ;:

@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

Вы хорошо заметили. Впоследствии я заметил, что в базе данных были некоторые ошибочные курсы, но у меня не было времени изучить их.
@thomvlau - похоже, вам не хватает шаблона редактора для вашего класса AssignedCategory.
Этот ответ потрясающий! ... действительно хорошая работа! ... большое спасибо за ваши усилия !! .. PnP
@ Yash Я видел различные способы, с помощью которых парни получают с сервера строки, разделенные запятыми, разделяют их и создают флажки с помощью js и снова отправляют строки cs на сервер. Как и в большинстве сценариев, существует несколько способов сделать это, но это способ .NET MVC, поэтому я полагаю, что это вопрос того, что вам наиболее удобно.
+1 Удивительный подробный ответ! Единственное, чего не хватает, это привязать курсы к контексту, иначе EF создаст дубликаты курсов в базе данных. ВAddOrUpdateCourses: var course = new Course { ... }; db.Courses.Attach(course); userProfile.Courses.Add(course);

Похожие вопросы