web-dev-qa-db-ja.com

MVC Createビューで多対多の関係データを保存する

作成ビューの結果を保存する多対多の関係にいくつかの問題があります。

コース(多対多の関係)を選択できるチェックリストを持つ新しいユーザープロファイルの作成ページを作成したいと思います。私のビューは、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-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);
}
33
PnP

Edit:私はこれをコード付きの3つのブログ投稿に書きました

  • パート1 ソリューションを設定し、新しいユーザーを作成します
  • パート2 コースを追加し、ユーザープロファイルと共に保存します
  • パート ユーザーとそのコースの編集と削除を許可します

Githubソース: https://github.com/cbruen1/mvc4-many-to-many


たとえば、命名規則の一部で慣習から少し外れていると思うので、適切と思われる箇所に変更を加えました。私の意見では、UserProfileの一部としてコースをポストバックする最良の方法は、後で説明するエディターテンプレートによってそれらをレンダリングすることです。

これをすべて実装する方法を次に示します。

(新しいコースを保存するときにバグを指摘してくれた@Slaumaに感謝します)。

  • モデルでは、「コース」クラスは単一のエンティティであり、通常はコースと呼ばれ、クラスコースのコレクションはコースと呼ばれます。
  • 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);
}

これは、ViewBagに入れないことを除いて、PopulateCourseDataと同じです。これは、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は関連付けを処理します。 OnModelCreatingで作成されたT_UserProfile_Courseテーブルに選択された各コースのレコードを追加します。投稿されたコースを表示するCreateUserProfileアクションの結果メソッドは次のとおりです。

Courses check boxes posted back to the controller

2つのコースを選択しました。コースが新しいユーザープロファイルオブジェクトに追加されていることがわかります。

Courses added to new userprofile object

73
Ciaran Bruen