2

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

  • HttpPost (api/Assessment/aID/groups) where a group already contains a list of users. When trying to accomplish this, 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) which displays its assigned groups and linked users.
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

  • As an aside to my comments below, attaching *pictures* of code is far, far less useful than just adding code snippets to the post; SO is designed to allow easy asking of code-related questions so it's actually easier for you and everyone else to copy/paste relevant bits of code than it is to post .png images... – GPW Nov 12 '20 at 13:55

1 Answers1

1

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.

GPW
  • 2,528
  • 1
  • 10
  • 22
  • We are using EF Core indeed, the issue you mentioned is something we've already resolved as indeed that would cause an object cycle. What's happening is when requesting a get or post two layers deep (assessment - user) it causes an object cycle, yet when adding an empty group to an assessment and users to a group afterwards, it seems to work properly. Thank for the help so far, what classes would help for providing information? – Gautierdebr Nov 11 '20 at 16:50
  • your issue is I think one of JSON serialisation. The objects you are attempting to return from your API calls aren't able to be serialised because they are similar in structure to the EF models, and have circular references (can't quite see where they are in your attached .png images; `GroupUser` and `GroupAssessment` classes aren't shown). What you should do is work out exactly what your API consumer wants from the API, and create a class which gives *only* that information. I've edited the answer to expand a bit on this. – GPW Nov 12 '20 at 13:52
  • I appreciate your answer, this has helped a lot! How would you have to create your HttpPost though? I guess you would be passing along a DTO object for the data and assign that data to the regular objects which are chained together. Yet when trying to do so, I seem to be getting a NullReference when creating the regular objects. – Gautierdebr Nov 12 '20 at 23:31
  • I guess at least one of the `User` objects in the `dto.Users` collection has a null address. this could be because it's not included in the input object, or something as simple as a typo (e.g. the input JSON has 'Adress' rather than 'Address'), but you should just check the address isn't null before trying to use it. There are plenty of posts on here about protecting against null reference exceptions. – GPW Nov 13 '20 at 09:54