Home > OS >  .NET Web API & EF Core relationships
.NET Web API & EF Core relationships

Time:09-20

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

How to install NuGet Packages

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; 
});
  • Related