修复 C# Web API 中的循环引用错误

Fixing Circular Reference Error in C# Web API

提问人:Yusuf 提问时间:10/25/2023 更新时间:10/25/2023 访问量:48

问:

我有一个用 C# 编写的相当简单的 Web API,使用 EF。这个想法几乎是一个待办事项应用程序,其具体关系是:一个用户可以有许多待办事项列表,每个待办事项列表可以有许多待办事项。

以下是我的数据模型:

public class User
{
    public Guid Id { get; set; }
    public string? Name { get; set; }
    public string? Email { get; set; }
    public string? Password { get; set; } // Be sure to properly hash and salt passwords
    public string? ProfilePhotoUrl { get; set; } // Store the URL to the user's profile photo
    public List<ToDoList>? ToDoLists { get; set; } // A user can have multiple to-do lists
}

public class ToDoList
{
    public Guid Id { get; set; }
    public string? Title { get; set; }
    public List<ToDo>? ToDos { get; set; } // Each to-do list contains multiple to-dos

    // Foreign Key to User
    public Guid UserId { get; set; }
    public User? User { get; set; }
}
public class ToDo
{
    public Guid Id { get; set; }
    public string? Name { get; set; }
    public string? Description { get; set; }
    public bool? InProgress { get; set; }
    public bool? IsComplete { get; set; }
    public DateTime Created { get; set; } = DateTime.Now;

    public ToDoList? ToDoList { get; set; } 
    public Guid? ToDoListId{ get; set; }
}

我有一个控制器,允许用户注册、登录等。我想做的一件事是检索用户可能拥有的每个待办事项列表。我在服务中有代码:

    public Task<User> GetToDoListsForUsers(Guid userId)
    {
        return _context.Users
            .Include(u => u.ToDoLists)
            .FirstOrDefaultAsync(u => u.Id == userId);
    }

然后由控制器调用:

    [HttpGet("{userId}/todolists")]
    public async Task<ActionResult<IEnumerable<ToDoList>>> GetToDoListsForUser(Guid userId)
    {
        var user = await _userService.GetToDoListsForUsers(userId);

        if (user == null)
        {
            return NotFound("User not found");
        }

        var toDoLists = user.ToDoLists;

        if (toDoLists.Count == 0)
        {
            return NotFound("user has no lists");
        }

        return Ok(toDoLists);
    }

但是,当我运行它时,我收到以下错误:


      An unhandled exception has occurred while executing the request.
      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: $.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.User.ToDoLists.Id.

我相信这是因为 User 具有对 ToDoList 的引用,而 ToDoList 具有对 User 的引用,但我需要这两种方式的引用,因为它们是外键。

我还在网上遇到了这个解决方案,以添加以下几行:

        var jsonSettings = new JsonSerializerOptions
        {
            ReferenceHandler = ReferenceHandler.Preserve,
        };

        var serializedUser = JsonSerializer.Serialize(user, jsonSettings);
        var deserializedUser = JsonSerializer.Deserialize<User>(serializedUser, jsonSettings);

        var toDoLists = deserializedUser.ToDoLists;

到控制器方法,但这也不起作用。

在解决这个问题方面有什么帮助吗?

C# asp.net ASP.NET-MVC 实体框架 循环引用

评论


答:

1赞 Guru Stron 10/25/2023 #1

TBH 找到的“解决方案”没有任何意义,使用相同的设置只会恢复引用。ReferenceHandler.Preserve

您可以只返回序列化的手动数据:

var serializedLists = JsonSerializer.Serialize(user.ToDoLists, jsonSettings);
return Content(serializedLists);

或者更好地全局应用设置:ReferenceHandler.Preserve

builder.Services.AddControllers()
    .AddJsonOptions(options =>
        options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve);

另一个值得考虑的选项(我默认使用的一个) - 创建 DTO 并映射到它们,因此不存在循环,响应只有所需的数据。

阅读更多内容 - 避免或控制 Entity Framework Core 中的循环引用

评论

1赞 Steve Py 10/25/2023
我建议使用 DTO 进行数据传输,而不是尝试发送实体。循环引用在数据域 PoV 中很有用,但在序列化 PoV 中是无用且有问题的,因此最好使用适合特定目的的类。有了序列化程序跟踪引用,并确保不编写循环引用,而只依次引用。这将产生遍历所有参考的成本,如果不需要或没有用,则可以避免使用 DTO 进行引用。ReferenceHandler.Preserve
0赞 Yusuf 10/26/2023
谢谢 - 将其添加到我的程序 .cs 中解决了这个问题,现在一切正常。Tbh - 我以前没有遇到过 DTO,但阅读它们绝对是处理这个问题的更好方法,也可能实现它。
0赞 Guru Stron 10/26/2023
@Yusuf很乐意帮忙!如果答案适合您 - 请随时投票并接受(左侧的复选标记)