A circular reference is a chain of object references that eventually points back to an earlier object. In ASP.NET Core web services, Entity Framework Core can create this cycle while loading related data: a Supplier contains Products, and each Product points back to its Supplier. JSON serialization then fails with an object-cycle exception. section 20.2
A related-data query that creates the cycle
A controller query such as context.Suppliers.Include(s => s.Products).FirstAsync(...) loads the supplier and its products. Entity Framework Core also populates each Product.Supplier navigation property, producing Supplier → Products → Supplier. Requesting /api/suppliers/1 can therefore fail during JSON serialization. section 20.2
Listing 20.4 Related data in the SuppliersController.cs file in the Controllers folder
using Microsoft.AspNetCore.Mvc;
using WebApp.Models;
using Microsoft.EntityFrameworkCore;
namespace WebApp.Controllers {
[ApiController]
[Route("api/[controller]")]
public class SuppliersController : ControllerBase {
private DataContext context;
public SuppliersController(DataContext ctx) {
context = ctx;
}
[HttpGet("{id}")]
public async Task<Supplier?> GetSupplier(long id) {
return await context.Suppliers
.Include(s => s.Products)
.FirstAsync(s => s.SupplierId == id);
}
}
}
Inspect the complete SuppliersController query and see where Include loads the related collection. section 20.2
Break the cycle before serialization rather than trying to prevent Entity Framework Core from creating it. After loading the supplier, set each product's Supplier property to null. The response can then contain the supplier and its products without recursively returning to the supplier. section 20.2.1
Listing 20.5 References in the SuppliersController.cs file in the Controllers folder
using Microsoft.AspNetCore.Mvc;
using WebApp.Models;
using Microsoft.EntityFrameworkCore;
namespace WebApp.Controllers {
[ApiController]
[Route("api/[controller]")]
public class SuppliersController : ControllerBase {
private DataContext context;
public SuppliersController(DataContext ctx) {
context = ctx;
}
[HttpGet("{id}")]
public async Task<Supplier?> GetSupplier(long id) {
Supplier supplier = await context.Suppliers
.Include(s => s.Products)
.FirstAsync(s => s.SupplierId == id);
if (supplier.Products != null) {
foreach (Product p in supplier.Products) {
p.Supplier = null;
};
}
return supplier;
}
}
}
Inspect the foreach loop that removes each Product.Supplier back-reference before the controller returns the result. section 20.2.1
The problem is specific to serialized web-service results. Related objects used by MVC views or Razor Pages are not serialized, so their navigation-property cycles do not cause this JSON failure. section 27.4.3 section 20.6


