web-dev-qa-db-fra.com

Enregistrement de données de relation plusieurs à plusieurs sur MVC Créer une vue

J'ai des problèmes avec une relation plusieurs-à-plusieurs pour enregistrer les résultats d'une vue de création.

Je veux créer une page pour un nouveau profil d'utilisateur qui a une liste de contrôle qui leur permet de choisir des cours (relation plusieurs à plusieurs). Ma vue prend les enregistrements de la base de données Courses et les montre tous avec des cases à cocher.

Une fois que l'utilisateur a publié les données, je souhaite mettre à jour mon modèle userprofile, ainsi que la relation plusieurs à plusieurs courses. C'est le code qui me manque!

Je suis nouveau chez MVC et j'ai fait des recherches mais je ne pouvais pas encore le faire.

Je suis cet exemple: http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/updating-related-data-with-the-entity-framework- dans une application asp-net-mvc

Voici le modèle:

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

J'ai également ajouté un ViewModel:

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

Voici le create Controller qui remplit les cases à cocher des cours:

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

Telle est la vue

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

Et c'est le contrôleur qui obtient les données (et où je veux les enregistrer). Il obtient comme paramètre string[] selectedCourse pour les cases à cocher:

[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);
}
33
PnP

Edit : J'ai écrit ceci dans 3 articles de blog avec du code

  • partie 1 configure la solution et crée un nouvel utilisateur
  • partie 2 ajoute les cours et les enregistre avec le profil utilisateur
  • partie permet l'édition et la suppression des utilisateurs et de leurs cours

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


Je pense que vous vous êtes un peu éloigné des conventions dans certains de vos noms, par exemple, j'ai donc apporté des modifications là où je le jugeais approprié. À mon avis, la meilleure façon de faire publier les cours dans le cadre du profil utilisateur est de les rendre rendus par un modèle d'éditeur que j'explique plus loin.

Voici comment je mettrais en œuvre tout cela:

(Merci à @Slauma d'avoir signalé un bug lors de l'enregistrement de nouveaux cours).

  • dans votre modèle, votre classe "Cours" est une entité unique et serait généralement nommée Cours, et une collection de cours de classe serait nommée Cours.
  • au lieu d'avoir AssignedCourseData dans le ViewBag, utilisez un modèle de vue
  • implémentez cela lors de la création d'un nouvel utilisateur - c'est-à-dire que vous avez une vue Create standard pour un profil utilisateur contenant un modèle de vue AssignedCourseData qui sera publié avec UserProfileViewModel.

À partir de la base de données, laissez la collection UserProfile telle quelle et nommez la collection Course Courses:

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

Dans la classe DbContext, remplacez la méthode OnModelCreating. Voici comment mapper la relation plusieurs à plusieurs entre UserProfile et 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);
}

Je voudrais également ajouter une classe d'initialisation simulée dans le même espace de noms qui vous donnera quelques cours pour commencer et signifie que vous n'avez pas à les ajouter manuellement chaque fois que votre modèle change:

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

Ajoutez cette ligne à Application_Start () Global.asax pour la démarrer:

Database.SetInitializer(new MockInitializer());

Voici donc le modèle:

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

Créez maintenant 2 nouveaux résultats d'action dans votre contrôleur pour créer un nouveau profil utilisateur:

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

Voici vos PopulateCourseData similaires à la façon dont vous les aviez, sauf ne les mettez pas dans le ViewBag - c'est maintenant une propriété sur 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;
}

Créer un modèle d'éditeur - dans votre dossier Views\Shared, créez un nouveau dossier appelé EditorTemplates si vous n'en avez pas déjà un. Ajoutez une nouvelle vue partielle appelée AssignedCourseData et collez le code ci-dessous. C'est le peu de magie qui rend et nomme toutes vos cases à cocher correctement - vous n'avez pas besoin d'un pour chaque boucle car le modèle de l'éditeur créera tous les éléments passés dans une collection:

@model AssignedCourseData
@using MySolution.ViewModels

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

Créez un modèle de vue de profil utilisateur dans votre dossier de modèles de vue - il contient une collection d'objets AssignedCourseData:

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

Ajoutez une nouvelle vue appelée CreateUserprofile.cshtml pour créer un profil utilisateur - vous pouvez cliquer avec le bouton droit dans la méthode de contrôleur CreateUserProfile déjà ajoutée et sélectionner "Ajouter une vue":

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

Cela rendra les noms de champ correctement afin qu'ils fassent partie du modèle de vue de profil utilisateur lorsque le formulaire est renvoyé au contrôleur. Les champs seront nommés comme tels:

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

Les autres champs seront nommés de la même manière, sauf qu'ils seront indexés respectivement avec 1 et 2. Enfin, voici comment enregistrer les cours dans le nouveau profil utilisateur lorsque le formulaire est renvoyé. Ajoutez cette méthode à votre contrôleur - elle est appelée à partir du résultat de l'action CreateUserProfile lorsque le formulaire est renvoyé:

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

Une fois que les cours font partie du profil utilisateur, EF s'occupe des associations. Il ajoutera un enregistrement pour chaque cours sélectionné à la table T_UserProfile_Course créée dans OnModelCreating. Voici la méthode de résultat de l'action CreateUserProfile montrant les cours publiés en retour:

Courses check boxes posted back to the controller

J'ai sélectionné 2 cours et vous pouvez voir que les cours ont été ajoutés au nouvel objet de profil utilisateur:

Courses added to new userprofile object

73
Ciaran Bruen