首页 > 解决方案 > ASP.NET MVC CORE - 多对多关系 - 创建视图

问题描述

因此,我遇到了两个实体之间存在多对多关系的情况。在中间,有一些字段可以描述关系。我将通过以下示例简化情况(粗体表示两个实体之间的简单字段:

public class Hobby
{
    public int HobbyID                            {get;set;}
    public string Name                            {get;set;}
    public string Type                            {get;set;}
    public ICollection<PersonHobby> PersonHobbies {get;set;}
}

public class Person
{
    public int PersonID                           {get;set;}
    public int Age                                {get;set;}
    public string Name                            {get;set;}
    public ICollection<PersonHobby> PersonHobbies {get;set;}
}

public class PersonHobby
{
    public int PersonHobbyID                      {get;set;}
    public int PersonID                           {get;set;}
    public int HobbyID                            {get;set;}
    **public int Rating                             {get;set;}**
}

我的目标是什么?有一个创建表单,它可以让我创建一个人并立即将评级归因于一个或多个爱好。创建表单应显示所有可用爱好的输入字段,但在提交时,应仅存储包含数据的字段。

我需要帮助来了解实现这一目标的最佳方法。到目前为止,我尝试在 Person Create View 和 Controller 上显示表单中的所有爱好。我这样做的方法是加载所有爱好并在创建控制器上将它们初始化为空。但是在提交时,我收到了如下错误:“模型绑定的复杂类型不能是抽象类型或值类型,并且必须有一个无参数的构造函数。”

谢谢你。

标签: asp.netasp.net-mvcasp.net-coremany-to-many

解决方案


开始的一点建议:我会在您的视图中使用视图模型而不是实体类,因为它会为您提供更大的灵活性并帮助您实现目标,而不会开始使用不属于那里的代码弄脏您的视图。显然,你可以用你的实体类来做,虽然提供了一些改变,但我不推荐它。

我相信你得到的错误是,因为你试图绑定到定义ICollection属性的实体类,而模型绑定器无法确定应该用来实例化它们的具体类型。例如,为了快速修复,您可以使用具体类型(例如CollectionList )更改您的ICollection。但同样,这是我的第一个建议......

所以,要以正确的方式做到这一点,这就是我要做的:

public class PersonViewModel
{
   public int Age {get;set;}
   public string Name {get;set;}
   public List<HobbyViewModel> Hobbies {get;set;}
}

public class HobbyViewModel
{
  public int HobbyID {get;set;}
  public string Name {get;set;}
  public string Type {get;set;}
  public int? Rating {get;set;}
}

public class PersonController
   : Controller
{
   ...

   [HttpGet]
   public IActionResult Create()
   {
      PersonViewModel viewModel = new PersonViewModel();
      viewModel.Hobbies = this.DbContext.Hobbies.Select(h => new HobbyViewModel() { Name = h.Name, Type = h.Type }).ToList();
      return this.View(viewModel);
   }

   [HttpPost]
   public IActionResult Create(PersonViewModel model)
   {
      if(!this.ModelState.IsValid)
         return this.BadRequest(model);
      Person person = new Person(){ Age = model.Age, Name = model.Name };
      foreach(HobbyViewModel hobbyViewModel in model.Hobbies)
      {
        //Check if the user has rated the hobby and if not, skip it
        if(!hobbyViewModel.Rating.HasValue)
          continue;
        person.PersonHobbies.Add(new PersonHobby(){ HobbyId = hobbyViewModel.HobbyId, Rating = hobbyViewModel.Rating.Value });
      }
      this.DbContext.Persons.Add(person);
      this.DbContext.SaveChanges();
      return this.RedirectToAction(nameof(Index));
   }
}

换句话说,在您的个人表单中,为视图模型中提供的每个爱好显示一个表单,并带有评级输入。当用户发布表单时,检查所有提供的爱好以检查用户是否已对其进行评分(即:检查是否设置了可为空的 Rating 属性),如果已设置,则将新的 PersonH​​obby 添加到生成的 Person 实体。瞧!

最后,关于您的实体模型的一些建议:

  • 忘记繁琐的PersonH​​obbies属性名称。它不是无处不在的,你应该尽可能地以无处不在为目标:它使你的代码更漂亮,更容易理解,因此更容易维护。例如,为 Person 实体命名您的属性 Hobbies,为 Hobby 实体命名 Persons 或 Voters。
  • 将您的关键属性重命名为 Id,因为它将被 EntityFramework 自动拾取为关键属性,没有配置和/或数据注释。此外,我希望 Person 的 Id 属性是......好吧,Person 的 id。无需指定它。
  • 我坚持,为了让你的生活更轻松,忘记在视图中使用你的实体类型,或者最糟糕的是,作为 Json/Xml 返回类型。您应该为您的视图使用视图模型,为您的 Json/Xml 返回类型使用数据传输对象 (DTO)。即使这个概念似乎违反了 KISS 原则,相信我,它也没有。随着时间的推移,它只会简化你的生活,尽管它在开始时可能看起来毫无意义。事实上,您的实体类型可能会随着时间的推移而演变,而您的视图可能不会,反之亦然。此外,您的视图通常需要比您的实体类型更多或不同的信息,并且保持此路径,您可能会发现自己开始通过调用等来检查您的视图,而它应该只包含视图逻辑。应该在您的控制器操作中进行调用,

推荐阅读