編集:コード付きの3つのブログ投稿にこれを書きました
- パート 1では、ソリューションをセットアップし、新しいユーザーを作成します
- パート 2コースを追加し、ユーザー プロファイルと共に保存します
- パート 3では、ユーザーとそのコースを編集および削除できます
Github ソース:
https://github.com/cbruen1/mvc4-many-to-many
たとえば、命名の一部で慣習から少し外れていると思うので、適切と思われる場所に変更を加えました。私の意見では、コースを UserProfile の一部としてポストバックする最善の方法は、後で説明するエディター テンプレートでコースをレンダリングすることです。
これをすべて実装する方法は次のとおりです。
(新しいコースを保存する際のバグを指摘してくれた @Slauma に感謝します)。
- モデルでは、「Courses」クラスは単一のエンティティであり、通常は Course という名前になり、クラス Course のコレクションは Courses という名前になります。
- ViewBag で AssignedCourseData を使用する代わりに、ビュー モデルを使用します。
- 新しいユーザーの作成中にこれを実装します。つまり、UserProfileViewModel でポストバックされる AssignedCourseData ビュー モデルを含む Userprofile の標準の作成ビューを用意します。
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 という名前の新しい部分ビューを追加し、以下のコードを貼り付けます。これは、すべてのチェック ボックスを正しくレンダリングして名前を付けるためのちょっとした魔法です。Editor テンプレートがコレクションに渡されたすべての項目を作成するため、for each ループは必要ありません。
@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 アクションの結果メソッドです。
2 つのコースを選択しました。これらのコースが新しいユーザー プロファイル オブジェクトに追加されていることがわかります。