Gautierdebr
Gautierdebr

Reputation: 21

How do you post a many-to-many-to-many relation in a REST API?

Using EF Core

We are trying to obtain all information of an assessment, which includes its groups and all assigned users. See the Database Diagram

What is working in following order;

  1. HttpPost (api/Assessment/aID/groups) of an empty group to an assessment
  2. HttpPost (api/Group/gID/users) of users to an existing group

What we are trying to accomplish (code referenced is a different example, yet same principle);

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

References:
User
Group
Assessment
AssessmentRepo

Upvotes: 2

Views: 435

Answers (1)

GPW
GPW

Reputation: 2626

Hard to tell with the details you're providing, but I'm guessing this is due to Having two-way navigation properties? Are you using EF here?

For example, if your User has a Navigation property allowing access to the user's Group, but a Group has a collection of User objects, then each of those users would themselves have the Group expressed within them... then when trying to express this it could easily get stuck in a cycle, e.g. a user would look like:

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

.. because a User has a Group, and the Group has a list of User objects, and each one of those has a Group... etc.

This is the sort of issue that comes from mixing your Data layer and DTO objects. Change your system so the objects returned by your REST methods are new objects designed for the requirements of the API/front-end. These objects may look very similar to your DB models (at least initially) but they should not be the same objects.

Create entirely new objects which don't have any logic or navigation properties, and exist only to pass information back to API consumers. For example, a simple class to give a list of user groups and the users in those groups may be defined as:

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

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

And then your controller action could do something like:

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

..So the thing being serialised for the response doesn't have any complicated relationships to negotiate, and more importantly a change to how you are internally storing and working with the data won't affect the external contract of your API - API consumers can continue to see exactly the same information but how you store and compile this can change drastically.

It is tempting to think "The Data I need to return is basically the same as how I store it internally, so just re-use these classes" but that's not a great idea & will only ever give problems in the long run.

To avoid having to (re-)write a lot of code to 'convert' one object into another, I'd recommend looking into something like AutoMapper as this can make that fairly easily re-usable & allow you to put all this 'Translation' stuff into one place.

Upvotes: 1

Related Questions