【问题标题】:How do you post a many-to-many-to-many relation in a REST API?您如何在 REST API 中发布多对多对多关系?
【发布时间】:2020-11-11 16:19:13
【问题描述】:

使用 EF Core

我们正在尝试获取评估的所有信息,包括其组和所有分配的用户。见 Database Diagram

按以下顺序工作;

  1. 将空组的 HttpPost (api/Assessment/aID/groups) 发送到评估
  2. HttpPost (api/Group/gID/users) 用户到现有组

我们正在努力完成的事情(引用的代码是一个不同的例子,但原理相同);

  • HttpPost (api/Assessment/aID/groups),其中组已包含用户列表。在尝试完成此操作时,a possible object cycle was detected which is not supported.
This piece of code is currently throwing a NullReference on Address
-------------------------------------------------------------------

   Group groupToCreate = new Group { Name = dto.Name, Description = dto.Description };

   foreach (var u in dto.Users)
   {
       groupToCreate.AddUser(new User
        {
             Name = u.Name,
             Email = u.Email,
             Address = new Address
             {
                 Country = u.Address.Country,
                 City = u.Address.City,
                 PostalCode = u.Address.PostalCode,
                 Street = u.Address.Street,
                 HouseNr = u.Address.HouseNr,
                 BusNr = u.Address.BusNr
             }
        });
    }

   _groupRepository.Add(groupToCreate);
   _groupRepository.SaveChanges();

   return groupToCreate;
  • HttpGet (api/Assessment) 显示其分配的组和链接的用户。
This seems to be working
------------------------
   groupList = _groups.Select(g => new GroupDTO
   {
       Name = g.Name,
       Description = g.Description,
       Users = g.GroupUsers.Select(u => new UserDTO
       {
           Name = u.User.Name,
           Email = u.User.Email,
           Address = new AddressDTO
           {
              Country = u.User.Address.Country,
              City = u.User.Address.City,
              PostalCode = u.User.Address.PostalCode,
              Street = u.User.Address.Street,
              HouseNr = u.User.Address.HouseNr,
              BusNr = u.User.Address.BusNr
           }
       }).ToList()
   }).ToList();

参考:
User
Group
Assessment
AssessmentRepo

【问题讨论】:

  • 顺便说一下下面我的cmets,附上代码的图片远不如仅仅在帖子中添加代码sn-ps有用; SO 旨在允许轻松询问与代码相关的问题,因此您和其他所有人实际上复制/粘贴相关代码位比发布 .png 图像更容易......

标签: c# api rest many-to-many relational


【解决方案1】:

很难用您提供的详细信息来判断,但我猜这是由于具有双向导航属性?您在这里使用 EF 吗?

例如,如果您的 User 有一个 Navigation 属性允许访问用户的 Group,但 Group 有一个 User 对象的集合,那么每个 这些 用户将自己在其中表达Group...然后在尝试表达这一点时,它很容易陷入一个循环,例如用户看起来像:

{
  "Name":"user name",
  "Group":{
     "Name":"group1",
     "Users":[
       {
          "Name":"user name",
          "Group":{
            "Name":"group1",
            "Users":{
              ....
            }
          }
       }
     ]
  }
}

.. 因为User 有一个Group,而Group 有一个User 对象列表,而这些 中的每一个都有一个Group...等等

这是由于混合数据层和 DTO 对象而产生的问题。更改您的系统,使您的 REST 方法返回的对象是为 API/前端要求而设计的新对象。这些对象可能看起来与您的数据库模型非常相似(至少最初是这样),但它们不应是相同的对象。

创建没有任何逻辑或导航属性的全新对象,并且仅存在以将信息传递回 API 使用者。例如,一个提供用户组列表的简单类以及这些组中的用户可以定义为:

public class UserDto
{
   public string UserName { get; set; }
   public IEnumerable<string> Groups { get; set; }
}

public class UserListDto
{
   public IEnumerable<UserDto> Users { get; set; }
}

然后您的控制器操作可以执行以下操作:

var users = userService.GetAllUsers();

var result = new UserListDto { 
    Users = users.Select(u => new UserDto{
      UserName = u.Name,
      Groups = u.Groups.Select(g => g.Name)
      }
   };

return Ok(result);

..因此,为响应序列化的事物没有任何复杂的关系需要协商,更重要的是,改变您内部存储和处理数据的方式不会影响您的 API 的外部合同 - API 使用者可以继续看到完全相同的信息,但您存储和编译这些信息的方式可能会发生巨大变化。

很容易想到“我需要返回的数据与我在内部存储它的方式基本相同,所以只需重新使用这些类”,但这不是一个好主意,而且从长远来看只会带来问题.

为避免不得不(重新)编写大量代码来将一个对象“转换”为另一个对象,我建议您研究一下 AutoMapper 之类的东西,因为这可以使其相当容易地重复使用并允许您将所有这个“翻译”的东西放在一个地方。

【讨论】:

  • 我们确实在使用 EF Core,您提到的问题是我们已经解决的问题,因为这确实会导致对象循环。发生的情况是,当请求获取或发布两层深度(评估 - 用户)时,它会导致对象循环,但是当将空组添加到评估并随后将用户添加到组时,它似乎可以正常工作。感谢到目前为止的帮助,哪些课程有助于提供信息?
  • 您的问题是我认为 JSON 序列化之一。您尝试从 API 调用返回的对象无法序列化,因为它们在结构上与 EF 模型相似,并且具有循环引用(无法完全看到它们在附加的 .png 图像中的位置;@ 987654333@ 和 GroupAssessment 类未显示)。您应该做的是准确地确定您的 API 使用者想要从 API 中得到什么,并创建一个提供该信息的类。我已经编辑了答案以对此进行扩展。
  • 感谢您的回答,这对您有很大帮助!但是,您将如何创建 HttpPost?我猜你会传递数据的 DTO 对象,并将该数据分配给链接在一起的常规对象。然而,当尝试这样做时,我似乎在创建常规对象时得到了 NullReference。
  • 我猜dto.Users 集合中至少有一个User 对象的地址为空。这可能是因为它没有包含在输入对象中,或者像错字一样简单(例如,输入 JSON 具有“地址”而不是“地址”),但您应该在尝试使用之前检查地址是否为空它。这里有很多关于防止空引用异常的帖子。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-19
  • 2019-01-02
  • 2011-09-13
相关资源
最近更新 更多