Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core API only returning first result of list

I have created a teams web api controller and trying to call the GET method to get the json result of all the teams in the database. But when I make the call I am only getting the first team back in the json but when I set a breakpoint on the return statement it has all 254 teams along with all of the games.

These are the two models that I am dealing with:

public class Team
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Icon { get; set; }
    public string Mascot { get; set; }
    public string Conference { get; set; }
    public int NationalRank { get; set; }

    public List<Game> Games { get; set; }
}

public class Game
{
    public string Id { get; set; }
    public string Opponent { get; set; }
    public string OpponentLogo { get; set; }
    public string GameDate { get; set; }
    public string GameTime { get; set; }
    public string TvNetwork { get; set; }
    public string TeamId { get; set; }

    public Team Team { get; set; }
}

When I do this:

[HttpGet]
public async Task<List<Team>> Get()
{
    var teams = await _context.Teams.ToListAsync();

    return teams;
}

I get all 254 teams but Game property is null because EF Core does not support lazy loading. So what I really want to do is add the .Include() like this:

[HttpGet]
public async Task<List<Team>> Get()
{
    var teams = await _context.Teams.Include(t => t.Games).ToListAsync();

    return teams;
}

This returns the first team with the first game but nothing else. Here is the json:

[
  {
    "id": "007b4f09-d4da-4040-be3a-8e45fc0a572b",
    "name": "New Mexico",
    "icon": "lobos.jpg",
    "mascot": "New Mexico Lobos",
    "conference": "MW - Mountain",
    "nationalRank": null,
    "games": [
      {
        "id": "1198e6b1-e8ab-48ab-a63f-e86421126361",
        "opponent": "vs Air Force*",
        "opponentLogo": "falcons.jpg",
        "gameDate": "Sat, Oct 15",
        "gameTime": "TBD ",
        "tvNetwork": null,
        "teamId": "007b4f09-d4da-4040-be3a-8e45fc0a572b"
      }
    ]
  }
]

When I set a break point on the return statement it shows that there are 254 teams and every team has their games populated correctly...but the json result does not reflect. Here is an image:

enter image description here

I have tried doing this both synchronously and asynchronously but getting the same result. Do you know why I am only getting one result back in the json but at the breakpoint it has all of the results?

like image 239
Aaron Avatar asked Aug 18 '16 17:08

Aaron


2 Answers

Add this to Startup.cs inside the public void ConfigureServices(IServiceCollection services) method:

services.AddMvc().AddJsonOptions(options => {             options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;         }); 

The issue was discussed https://github.com/aspnet/Mvc/issues/4160 and https://github.com/aspnet/EntityFramework/issues/4646 also see circular reference

like image 123
adem caglin Avatar answered Oct 11 '22 10:10

adem caglin


It is worth noting that if you do control the json output like with inline JsonSerializerSettings option,

[HttpGet] public async Task<IActionResult> Get([FromForm]bool strip_nulls = true) {     var teams = await _context.Teams.Include(t => t.Games).ToListAsync();      return Json(teams, new JsonSerializerSettings() {           NullValueHandling = strip_nulls ? NullValueHandling.Ignore : NullValueHandling.Include     }); } 

Simply putting the suggested solution from @adeam-caglin, which is not wrong in approach, will not work. You must also set the setting in your return. For example.

[HttpGet] public async Task<IActionResult> Get([FromForm]bool strip_nulls = true) {     var teams = await _context.Teams.Include(t => t.Games).ToListAsync();      return Json(teams, new JsonSerializerSettings() {           NullValueHandling = strip_nulls ? NullValueHandling.Ignore : NullValueHandling.Include,          ReferenceLoopHandling = ReferenceLoopHandling.Ignore     }); } 

It basically nulls out, not adds to the settings you set on the Startup.cs. This also gives you a road map to not globally alter your output but do it case by case.

EDIT

I would also like to take a moment and clarify what happens when you use ReferenceLoopHandling.Ignore, you are asking to drink from the fire hose but hoping it will be a controlled flow. If you have a highly developed modeling, you will more then likely have a set where you think you going to get your intended entity and it's child list, but if those list items also have children, or other parents then you will load those. Lets say you have

Teams>Players>Contacts Games>Teams 

This would produce a heck of a json nested return. I would have been wanting a flat Game>Teams but would end up with Games>Teams>Players. This is a simple example but it is easy to see how you could go from a couple KB of data to never ending loop that chokes out the client consuming the results.

This is means you will need to control that flow your self. To get that flatter json return expected you will also need to incorporate .AsNoTracking() on the .Include(x => x.Games)

As a very simple example, you would need to do something like:

[HttpGet] public async Task<IActionResult> Get([FromForm]bool strip_nulls = true) {     var teams = _context.Teams.AsQueryable();     teams = teams.Include(t => t.Games).AsNoTracking();     Teams _return = await teams.ToListAsync();     return Json(_return, new JsonSerializerSettings() {           NullValueHandling = strip_nulls ? NullValueHandling.Ignore : NullValueHandling.Include,          ReferenceLoopHandling = ReferenceLoopHandling.Ignore     }); } 
like image 32
Quantum Avatar answered Oct 11 '22 11:10

Quantum