Keywords: Object Cycle Reference | Serialization Error | .NET Core | System.Text.Json | Newtonsoft.Json | Entity Framework Core
Abstract: This article provides an in-depth analysis of System.Text.Json serialization errors caused by object cycle references in .NET Core 3.0 and later versions. By comparing different solutions using Newtonsoft.Json and System.Text.Json, it offers detailed configuration methods in Startup.cs, including the usage scenarios and implementation details of ReferenceHandler.IgnoreCycles and ReferenceLoopHandling.Ignore. The article also discusses the root causes of circular references and preventive measures to help developers completely resolve such issues.
Problem Background and Error Analysis
In ASP.NET Core application development, when loading entities with bidirectional navigation properties using Entity Framework Core, developers often encounter serialization errors caused by object cycle references. The specific error message is: System.Text.Json.JsonException: A possible object cycle was detected which is not supported. This error typically occurs when entities have one-to-many or many-to-many relationships with navigation properties defined on both sides.
Root Causes of Circular References
Taking a restaurant reservation system as an example, the entity classes are defined as follows:
public class Restaurant {
public int RestaurantId { get; set; }
public string Name { get; set; }
public List<Reservation> Reservations { get; set; }
}
public class Reservation {
public int ReservationId { get; set; }
public int RestaurantId { get; set; }
public Restaurant Restaurant { get; set; }
}
When executing query operations:
var restaurants = await _dbContext.Restaurants
.AsNoTracking()
.Include(m => m.Reservations)
.ToListAsync();
The serialization process enters an infinite loop: Restaurant → Reservations → Restaurant → Reservations... System.Text.Json does not support this type of circular reference by default, thus throwing an exception.
Solution 1: Using Newtonsoft.Json
For .NET Core 3.0 projects, circular reference handling can be achieved by installing the Microsoft.AspNetCore.Mvc.NewtonsoftJson package:
// Install NuGet package
Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson -Version 3.0.0
// Configure in Startup.cs
services.AddControllersWithViews()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
This configuration ignores circular references and outputs null values when encountering duplicate objects, thereby avoiding serialization errors.
Solution 2: Using System.Text.Json New Features
In .NET 6 and later versions, System.Text.Json provides native circular reference handling capabilities:
// Method 1: Global configuration
services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler =
ReferenceHandler.IgnoreCycles;
options.JsonSerializerOptions.WriteIndented = true;
});
// Method 2: Custom output formatter
services.AddControllers(options =>
{
options.OutputFormatters.RemoveType<SystemTextJsonOutputFormatter>();
options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(
new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
ReferenceHandler = ReferenceHandler.Preserve,
}));
});
Comparative Analysis of Both Solutions
Newtonsoft.Json Solution:
- Advantages: Mature and stable, clear processing logic
- Disadvantages: Requires additional package installation, compatibility issues with System.Text.Json
- Applicable scenarios: Quick fixes for existing projects
System.Text.Json Solution:
- Advantages: Native support, better performance
- Disadvantages: Full functionality available only in .NET 6+
- Applicable scenarios: New project development,追求最佳性能
Practical Application Example
Here is a complete serialization example for an employee management system:
public class Employee
{
public string Name { get; set; }
public Employee Manager { get; set; }
public List<Employee> DirectReports { get; set; }
}
// Create circular reference relationship
Employee tyler = new() { Name = "Tyler Stein" };
Employee adrian = new() { Name = "Adrian King" };
tyler.DirectReports = new List<Employee> { adrian };
adrian.Manager = tyler;
// Serialize using IgnoreCycles
JsonSerializerOptions options = new()
{
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = true
};
string json = JsonSerializer.Serialize(tyler, options);
Console.WriteLine(json);
In the output result, circular reference parts will be set to null, avoiding serialization errors.
Best Practice Recommendations
1. Data Model Design: Consider serialization requirements during entity design phase, avoid unnecessary bidirectional navigation
2. DTO Pattern: For API return data, recommend using dedicated Data Transfer Objects instead of directly returning EF Core entities
3. Version Selection: New projects recommend using .NET 6+ and System.Text.Json, existing projects can choose migration strategies based on actual situation
4. Performance Considerations: IgnoreCycles loses some data, Preserve adds metadata, need to balance based on specific requirements
Conclusion
Object cycle references are common issues in .NET Core Web API development. By properly configuring serialization options, these problems can be effectively resolved. Developers are advised to choose appropriate solutions based on project requirements and version constraints, and consider the impact of data serialization during the design phase to build more robust applications.