保存多对多关系数据MVC创建视图 [英] Saving Many to Many relationship data on MVC Create view

查看:172
本文介绍了保存多对多关系数据MVC创建视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我想为一个新的用户个人资料创建一个页面它有一个清单,让他们选择课程(多对多关系)。
我的视图从课程数据库中获取记录,并将其全部显示为复选框。



一旦用户发布数据,我想更新我的 userprofile 模型,以及$ code>课程多对多关系。这是我错过的代码!



我是MVC的新人,我一直在研究,但我还没有做到。



我正在关注这个例子:
http://www.asp.net/mvc/tutorials/getting-started- with-ef-using-mvc / updates-related-data-with-the-entity-framework-in-an-asp-net-mvc-application



这是模型:

  public class UserProfile 
{
public int Id {get;组; }
public string Name {get;组; }
public virtual ICollection<课程> usercourses {get;组; }
}

public class课程
{
public int CourseID {get;组; }
public string CourseDescripcion {get;组; }
public virtual ICollection< UserProfile> UserProfiles {get;组; }
}

public class UserProfileDBContext:DbContext
{
public DbSet< UserProfile> UserProfiles {get;组; }
public DbSet<课程> usrCourses {get;组;
}

我还添加了一个ViewModel:

 命名空间Mysolution.ViewModels 
{
public class AssignedCourseData
{
public int CourseID {get;组; }
public string CourseDescription {get;组; }
public bool Assigned {get;组; }
}
}

这是创建填充课程复选框的控制器:

  public ActionResult Create()
{
PopulateCoursesData();
return View();
}

private void PopulateCoursesData()
{
var CoursesData = db.usrCourses;
var viewModel = new List< AssignedCourseData>();
foreach(CoursesData中的var项)
{
viewModel.Add(new AssignedCourseData {
CourseID = item.CourseID,
CourseDescription = item.CourseDescription,
Assigned = false});
}
ViewBag.CoursePopulate = viewModel;
}

这是视图

  @ {
int cnt = 0;
列表< MySolution.ViewModels.AssignedCourseData>课程= ViewBag.CoursePopulate;

foreach(课程中的var课程)
{
< input type =checkboxname =selectedCoursevalue =@ course.CourseID/>
@ course.CourseDescription
}
}

这是获取数据的控制器(以及我要保存的位置)。
它作为参数 string [] selectedCourse 的复选框:

  [HttpPost] 
public ActionResult Create(UserProfile userprofile,string [] selectedCourse)
{
if(ModelState.IsValid)
{
db.UserProfiles 。新增(USERPROFILE);

// TO DO:将数据从许多数据保存到许多(这是我不能做的)

db.SaveChanges();
}

return View(userprofile);
}


解决方案

strong>:我已经在3篇博客文章中写了代码





Github源:
https://github.co m / cbruen1 / mvc4-many-to-many






我想你已经从惯例在某些你的命名中有一点点,所以我已经做了修改,我认为合适。在我看来,将课程作为UserProfile的一部分退回的最佳方法是让它们由编辑器模板呈现,我将进一步解释。



以下是我将如何实现所有这些:



(感谢@Slauma指出存储时出现的错误您的模型中的




  • ,您的课程课程是一个单一实体,通常将被命名为课程,课程将被命名为课程。

  • 而不是在ViewBag中使用AssignedCourseData,使用视图模型

  • 在创建新用户时实现此功能 - 即为Userprofile设置标准的创建视图包含一个AssignedCourseData视图模型,该模型将用UserProfileViewModel发回。



从DB开始,留下UserProfile集合,并命名课程集合课程:

  public DbSet< UserProfile> UserProfiles {get;组; } 
public DbSet< Course>课程{get;组;

在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 =初学者篮子编织};
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;组; }
public string Name {get;组; }
public virtual ICollection< Course>课程{get;组; }
}

public class课程
{
public int CourseID {get;组; }
public string CourseDescripcion {get;组; }
public virtual ICollection< UserProfile> UserProfiles {get;组; }
}

现在,在您的控制器中创建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项目)
{
assignedCourses.Add(new AssignedCourseData
{
CourseID = item.CourseID,
课程描述= item.CourseDescripcion,
Assigned = false
});
}

返回assignCourses;
}

创建一个编辑器模板 - 在您的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对象的集合: / p>

  public class UserProfileViewModel 
{
public int UserProfileID {get;组; }
public string Name {get;组; }
public virtual ICollection< AssignedCourseData>课程{get;组; }
}

添加一个名为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)

//渲染复选框使用编辑器模板
@ Html.EditorFor(x => x.Courses)

< / fieldset>

< p>
< input type =submitvalue =Create/>
< / p>
}

这将正确显示字段名称,以便它们是用户的一部分当窗体发回控制器时,配置文件视图模型。这些字段将被命名为:

 < fieldset> 
< input data-val =truedata-val-number =字段CourseID必须是一个数字。 data-val-required =需要CourseID字段。 id =Courses_0__CourseIDname =Courses [0] .CIDIDtype =hiddenvalue =1/>
< input data-val =truedata-val-required =分配字段是必需的。 id =Courses_0__Assignedname =Courses [0] .Assignedtype =checkboxvalue =true/>< input name =Courses [0] .Assignedtype =hiddenvalue =false />
观鸟
< / fieldset>

其他字段将被命名类似,除了将索引为1和2。最后,当表单发回时,最后如何将课程保存到新的用户配置文件中。将此方法添加到您的控制器 - 这是从CreateUserProfile操作结果中调用的,当表单发回时:

  private void AddOrUpdateCourses UserProfile userProfile,IEnumerable&AssignedCourseData> assignedCourses)
{
foreach(被赋值的var assignCourse)
{
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门课程,您可以看到课程已添加到新用户个人资料对象中:




I have some issues with a Many-to-many relationship saving the results of a create view.

I want to do a create page for a new user profile that has a checklist that lets them choose courses (many to many relationship). My view takes the records from the Courses database and shows them all with checkboxes.

Once the user posts the data, I want to update my userprofile model, and also the courses many-to-many relationship. That's the code that I have missing!

I'm new at MVC and I've been researching but I couldn't do it yet.

I am following this example: 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

This is the model:

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

I also added a ViewModel:

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

This is the create Controller that populates the courses checkboxes:

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

This is the view

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

And this is the controler that gets the data (and where I want to save it). It gets as a parameter string[] selectedCourse for the checkboxes:

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

解决方案

Edit: I've written this up in 3 blog posts with code

  • part 1 sets up the solution and creates a new user
  • part 2 adds the courses and saves them with the user profile
  • part 3 allows editing and deletion of users and their courses

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


I think you've strayed from conventions a little bit in some of your naming for example so I've made changes where I saw fit. In my opinion the best way to have the courses posted back as part of the UserProfile is to have them rendered by an Editor Template which I explain further on.

Here's how I would implement all this:

(Thanks to @Slauma for pointing out a bug when saving new courses).

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

Starting from the DB leave the UserProfile collection as is and name the Course collection Courses:

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

In the DbContext class override the OnModelCreating method. This is how you map the many to many relationship between UserProfile and 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);
}

I would also add a mock initializer class in the same namespace that will give you some courses to start with and means you don't have to manually add them every time your model changes:

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

Add this line to Application_Start() Global.asax to kick start it:

Database.SetInitializer(new MockInitializer());

So here's the model:

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

Now create 2 new action results in your Controller to create a new user profile:

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

Here's your PopulateCourseData similar to how you had it except don't put in in the ViewBag - it's now a property on the 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;
}

Create an Editor Template - in your Views\Shared folder create a new folder called EditorTemplates if you don't already have one. Add a new partial view called AssignedCourseData and paste the code below. This is the bit of magic that renders and names all your check boxes correctly - you don't need a for each loop as the Editor template will create all the items passed in a collection:

@model AssignedCourseData
@using MySolution.ViewModels

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

Create a user profile view model in your view models folder - this has a collection of AssignedCourseData objects:

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

Add a new view called CreateUserprofile.cshtml to create a user profile - you can right click in the already added CreateUserProfile controller method and select "Add View":

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

This will render the field names correctly in order that they are part of the user profile view model when the form is posted back to the Controller. The fields will be named as such:

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

The other fields will be named similarly except will be indexed with 1 and 2 respectively. Finally here's how to save the courses to the new user profile when the form is posted back. Add this method to your Controller - this is called from the CreateUserProfile action result when the form is posted back:

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

Once the courses are part of the user profile EF takes care of the associations. It will add a record for each course selected to the T_UserProfile_Course table created in OnModelCreating. Here's the CreateUserProfile action result method showing the courses posted back :

I selected 2 courses and you can see that the courses have been added to the new user profile object:

这篇关于保存多对多关系数据MVC创建视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆