0

I am building out a demo for an API in .NET Core and constructing a nested JSON object that needs to get constructed through a series of LINQ queries.

My current issue is that when i get about 4 layers deep, I want to customize what actually gets serialized, more specifically, i want to "Not Include" specific navigation properties for this query specifically, but not for general purpose, only for this specific query.

My first thought was to do a DTO, but that seems like an unnecessary extra model just for this one specific case...i'd like to work with LINQ directly to manipulate for my result.

I have already added the following to my Startup.cs file to avoid cycles:

            services.AddControllers().AddNewtonsoftJson(options =>
            options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
            );

I have 5 models currently in action that look like this:

 public class Hotel
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string StreetAddress { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Phone { get; set; }
        public List<HotelRooms> HotelRooms { get; set; }
    }

    public class Room
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public Layout Layout { get; set; }

        public List<RoomAmenities> RoomAmenities { get; set; }
        public List<HotelRooms> HotelRooms { get; set; }
    }

    public class HotelRooms
    {
        public int HotelID { get; set; }
        public int RoomNumber { get; set; }
        public int RoomID { get; set; }
        public decimal Rate { get; set; }
        public bool PetFriendly { get; set; }
        public Hotel Hotel { get; set; }
        public Room Room { get; set; }
    }

    public class Amenities
    {
        public int ID { get; set; }
        public string Name { get; set; }

        // Navigation Properties
        public List<RoomAmenities> RoomAmenities { get; set; }
    }

    public class RoomAmenities
    {
        public int RoomID { get; set; }
        public int AmenitiesID { get; set; }

        public Room Room { get; set; }
        public Amenities Amenity { get; set; }
    }

In my services, i have this logic that is actually doing the querying:

        public async Task<Hotel> GetById(int id)
        {
            var hotel = await _context.Hotel.FindAsync(id);
            // Get a list of rooms
            var rooms = await _context.HotelRooms.Where(r => r.HotelID == id)
                                            .Include(d => d.Room)
                                            .ThenInclude(a => a.RoomAmenities)
                                            .ThenInclude(x => x.Amenity).ToListAsync();
            hotel.HotelRooms = rooms;

            return hotel;
        }

The current output is:

{
    "id": 1,
    "name": "Amanda's Hotel",
    "streetAddress": "123 CandyCane Lane",
    "city": "Seattle",
    "state": "WA",
    "phone": "123-456-8798",
    "hotelRooms": [
        {
            "hotelID": 1,
            "roomNumber": 101,
            "roomID": 2,
            "rate": 75.00,
            "petFriendly": false,
            "room": {
                "id": 2,
                "name": "Queen Suite",
                "layout": 2,
                "roomAmenities": [
                    {
                        "roomID": 2,
                        "amenitiesID": 1,
                        "amenity": {
                            "id": 1,
                            "name": "Coffee Maker",
                            "roomAmenities": [
                                {
                                    "roomID": 1,
                                    "amenitiesID": 1,
                                    "room": {
                                        "id": 1,
                                        "name": "Princess Suite",
                                        "layout": 1,
                                        "roomAmenities": [
                                            {
                                                "roomID": 1,
                                                "amenitiesID": 2,
                                                "amenity": {
                                                    "id": 2,
                                                    "name": "Mini Bar",
                                                    "roomAmenities": []
                                                }
                                            }
                                        ],
                                        "hotelRooms": [
                                            {
                                                "hotelID": 1,
                                                "roomNumber": 123,
                                                "roomID": 1,
                                                "rate": 120.00,
                                                "petFriendly": true
                                            }
                                        ]
                                    }
                                }
                            ]
                        }
                    }
                ],
                "hotelRooms": []
            }
        },
        {
            "hotelID": 1,
            "roomNumber": 123,
            "roomID": 1,
            "rate": 120.00,
            "petFriendly": true,
            "room": {
                "id": 1,
                "name": "Princess Suite",
                "layout": 1,
                "roomAmenities": [
                    {
                        "roomID": 1,
                        "amenitiesID": 1,
                        "amenity": {
                            "id": 1,
                            "name": "Coffee Maker",
                            "roomAmenities": [
                                {
                                    "roomID": 2,
                                    "amenitiesID": 1,
                                    "room": {
                                        "id": 2,
                                        "name": "Queen Suite",
                                        "layout": 2,
                                        "roomAmenities": [],
                                        "hotelRooms": [
                                            {
                                                "hotelID": 1,
                                                "roomNumber": 101,
                                                "roomID": 2,
                                                "rate": 75.00,
                                                "petFriendly": false
                                            }
                                        ]
                                    }
                                }
                            ]
                        }
                    },
                    {
                        "roomID": 1,
                        "amenitiesID": 2,
                        "amenity": {
                            "id": 2,
                            "name": "Mini Bar",
                            "roomAmenities": []
                        }
                    }
                ],
                "hotelRooms": []
            }
        }
    ]
}

This is much too nested for me, i'd like the inner "roomAmenities" inside the "amenity" object to not be included. I'd like my object to look like this:

{
  "id": 1,
  "name": "Amanda's Hotel",
  "streetAddress": "123 CandyCane Lane",
  "city": "Seattle",
  "state": "WA",
  "phone": "123-456-8798",
  "hotelRooms": [
    {
      "hotelID": 1,
      "roomNumber": 101,
      "roomID": 2,
      "rate": 75,
      "petFriendly": false,
      "room": {
        "id": 2,
        "name": "Queen Suite",
        "layout": 2,
        "roomAmenities": [
          {
            "roomID": 2,
            "amenitiesID": 1,
            "amenity": {
              "id": 1,
              "name": "Coffee Maker"
            }
          }
        ],
        "hotelRooms": []
      }
    },
    {
      "hotelID": 1,
      "roomNumber": 123,
      "roomID": 1,
      "rate": 120,
      "petFriendly": true,
      "room": {
        "id": 1,
        "name": "Princess Suite",
        "layout": 1,
        "roomAmenities": [
          {
            "roomID": 1,
            "amenitiesID": 1,
            "amenity": {
              "id": 1,
              "name": "Coffee Maker"
            }
          },
          {
            "roomID": 1,
            "amenitiesID": 2,
            "amenity": {
              "id": 2,
              "name": "Mini Bar"
            }
          }
        ],
        "hotelRooms": []
      }
    }
  ]
}

Does anyone have any guidance on how i can achieve this with EFCore and LINQ?

  • 1
    >My first thought was to do a DTO That's the correct approach. Infact I would almost go as far to say that you shouldn't really return EF Models from your API if you can help it (Otherwise everytime you change your data model, your API contract changes when it may not need to). – MindingData Feb 19 '20 at 20:14
  • Ah Thanks! You are definitely right, a DTO is the best approach. – buttercup_byte Feb 19 '20 at 23:29
  • Have you try with not include `amenity` as `var rooms = await _context.HotelRooms.Where(r => r.HotelID == id).Include(d => d.Room).ThenInclude(a => a.RoomAmenities).ToListAsync();`? – Selim Yildiz Feb 20 '20 at 05:31

1 Answers1

0

you can check this post What is the difference between PreserveReferencesHandling and ReferenceLoopHandling in Json.Net?

I see that you have already implemented referenceloopHandling, but even when you are using that you can only make the inner reference as null but not totally remove the key- value pair from json

kannangokul
  • 84
  • 1
  • 10