I am trying to create a small app that presents a simple model of employee data.
To sum up, an employee is assigned to a team, and a team is assigned to a business unit. So when returning the employee record I would like to return both team and business unit (which the employee is a member of by proxy of the team).
Currently I cannot even get the team return by the app.
Models:
[Table("Employees")]
public class Employee
{
[Key]
public long EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? DateOfBirth { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public int TeamId { get; set; }
public Team Team { get; set; }
}
[Table("BusinessUnits")]
public class BusinessUnit
{
[Key]
public int BusinessUnitId { get; set; }
public string Name { get; set; }
public long? DirectorEmployeeId { get; set; }
public virtual List<Team>? Teams { get; set; }
}
[Table("Teams")]
public class Team
{
[Key]
public int TeamId { get; set; }
public string Name { get; set; }
public int BusinessUnitId { get; set; }
public virtual BusinessUnit BusinessUnit { get; set; }
public virtual List<Employee>? Employees { get; set; }
}
EmployeeContext:
public DbSet<Employee> Employees { get; set; }
public DbSet<BusinessUnit> BusinessUnits { get; set; }
public DbSet<Team> Teams { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Team>()
.HasOne(b => b.BusinessUnit)
.WithMany(t => t.Teams)
.HasForeignKey(b => b.BusinessUnitId);
modelBuilder.Entity<Employee>()
.HasOne(t => t.Team)
.WithMany(e => e.Employees)
.HasForeignKey(t => t.TeamId);
}
Web API controller:
[HttpGet]
public async Task<ActionResult<IEnumerable<Employee>>> GetEmployees()
{
return await _context.Employees.Include(e => e.Team).ToListAsync();
}
I get the exception:
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles.
Path: $.Team.Employees.Team.Employees.Team.Employees.Team.Employees.Team.Employees.Team.Employees.Team.Employees.Team.Employees.Team.Employees.Team.Employees.EmployeeId.
What the heck am I doing wrong?
CodePudding user response:
Install the NuGet package Microsoft.AspNetCore.Mvc.NewtonsoftJson
And add this section on Program.cs file if you using asp.net core 6 OR Startup.cs file if you using asp.net core 5 or below
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
CodePudding user response:
You're receiving that error because 'Employee' has a collection of 'Team' and 'Team' has a collection of 'Employee'. When the JSON is being serialized it generates the Employee object, the Team object, then a collection 'Employee', which then starts the loop over again as it tries to serialize 'Employee' again for each employee that's part of the 'Team' object.
Option 1: you can add an annotation onto the Employees collection on Team so that the serializer will ignore the Employees object when serializing.
[JsonIgnore]
public virtual List<Employee>? Employees { get; set; }
Option 2: You're likely best creating a Data Transfer Object (DTO) and populating that with LINQ so you're not returning the raw data to the end user and also not building a circular loop (by omitting the list object(s) entirely)
public class EmployeeDto
{
public long EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? DateOfBirth { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public TeamDto Team { get; set; }
}
public class TeamDto
{
public int TeamId { get; set; }
public string Name { get; set; }
public int BusinessUnitId { get; set; }
public virtual BusinessUnit BusinessUnit { get; set; }
}
Then do:
[HttpGet]
public async Task<ActionResult<IEnumerable<EmployeeDto>>> GetEmployees()
{
return await _context.Employees.Include(e => e.Team)
.Select(x => new EmployeeDto()
{
EmployeeId = x.EmployeeId,
...
})
.ToListAsync();
}
CodePudding user response:
For System.Text.Json
Program.cs:
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});