如何解决 .Net Framework MVC EntityFramework 自引用循环错误

How to solve .Net Framework MVC EntityFramework Self Reference Loop Error

我有书 class 包括作者,我的作者 class 也包括书。因此,当我尝试将 book 转换为 json 字符串时,它给出了引用循环错误。所以我这样做了

public IHttpActionResult GetAllForOfficer()
    {
        Library library = _libraryManager.GetById(ProviderAuthorization.libraryId);
        List<Book> books = _bookManager.GetAll().Where(x=>x.Libraries.Contains(library)).ToList();
        
        return Ok(JsonConvert.SerializeObject(books, Formatting.Indented,
                    new JsonSerializerSettings()
                    {
                        ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                    }));
    }

而这个 returns 输出是那样的

"[\r\n  {\r\n    \"Authors\": [\r\n      {\r\n        \"Books\": [],\r\n        \"Id\": 4,\r\n        \"Name\": \"Texe Marrs\"\r\n      }\r\n    ],\r\n    \"Categories\": [],\r\n    \"Comments\": [],\r\n    \"Libraries\": [\r\n      {\r\n        \"Books\": [\r\n          {\r\n            \"Authors\": [\r\n              {\r\n                \"Books\": [\r\n                  {\r\n                    \"Authors\": [],\r\n                    \"Categories\": [\r\n                      {\r\n                        \"Books\": [],\r\n                        \"Id\": 5,\r\n                        \"Name\": \"dede\",\r\n                        \"ClickCounter\": 0\r\n                      },\r\n                      {\r\n                        \"Books\": [],\r\n                        \"Id\": 6,\r\n                        \"Name\": \"asas\",\r\n                        \"ClickCounter\": 0\r\n                      }\r\n                    ],\r\n                    \"Comments\": [\r\n                      {\r\n                        \"User\": {\r\n                          \"Comments\": [],\r\n                          

我有这样的书class

public int Id { get; set; }

    [Required]
    [StringLength(10)]
    public string ISBN10 { get; set; }

    [Required]
    [StringLength(13)]
    public string ISBN13 { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public string Publisher { get; set; }

    public int? NumberOfPages { get; set; }

    public int? Revision { get; set; }

    public int? LatestRevision { get; set; }

    [StringLength(50)]
    public string Language { get; set; }

    [Column(TypeName = "date")]
    public DateTime? CreateDate { get; set; }

    public string Description { get; set; }

    [Column(TypeName = "image")]
    public byte[] Image { get; set; }

    public int ClickCounter { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Comment> Comments { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<UserBook> UserBooks { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Author> Authors { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Category> Categories { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Library> Libraries { get; set; }

我的输出看起来有点奇怪。它应该像“作者”:[“姓名”:“皮特马克”]。但是有一些反斜杠。我想我在将 class 转换为 json 时遇到了问题。我该如何解决?

您不需要任何特殊的转换,net 会为您完成。如果您试图使 json 看起来漂亮,那么在服务器端没有任何意义。在此之后,您将必须在客户端对其进行反序列化或解析。

public IHttpActionResult GetAllForOfficer()
{
        Library library = _libraryManager.GetById(ProviderAuthorization.libraryId);
        List<Book> books = _bookManager.GetAll().Where(x=>x.Libraries.Contains(library)).ToList();
        
        return Ok(books);
}

解决问题的一种快速方法是将 [JsonIgnore] 属性添加到 Book class 中的作者、类别和图书馆属性(以及可能的其他一些属性你的其他 classes 也是)。

更好的方法是为这些属性编写自定义 JsonConverter。例如,Book class 可以使用 AuthorNameConverter,它在序列化时将 Author 对象转换为带有作者姓名的 string。您可以将该转换器添加为 Authors 属性 的属性,或将其添加到 JsonSerializerSettings 的可用转换器中。

您可以在此处找到 JsonConverter 的示例:https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm

将其添加为属性:

[JsonProperty(ItemConverterType = typeof(AuthorNameConverter))]
public virtual ICollection<Author> Authors { get; set; }

感谢所有说我实际上不需要任何转换的人。我使用数据传输对象解决了这个问题。我创建了相同的 classes。并通过不均衡子 class 的关系对象(类型为 ICollection)将其转换为 dto。

这是我的图书实体。

public int Id { get; set; }

    [Required]
    [StringLength(10)]
    public string ISBN10 { get; set; }

    [Required]
    [StringLength(13)]
    public string ISBN13 { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public string Publisher { get; set; }

    public int? NumberOfPages { get; set; }

    public int? Revision { get; set; }

    public int? LatestRevision { get; set; }

    [StringLength(50)]
    public string Language { get; set; }

    [Column(TypeName = "date")]
    public DateTime? CreateDate { get; set; }

    public string Description { get; set; }

    [Column(TypeName = "image")]
    public byte[] Image { get; set; }

    public int ClickCounter { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Comment> Comments { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<UserBook> UserBooks { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Author> Authors { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Category> Categories { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Library> Libraries { get; set; }

这里是 BookDto class。

public int Id { get; set; }

    public string ISBN10 { get; set; }

    public string ISBN13 { get; set; }

    public string Name { get; set; }

    public string Publisher { get; set; }

    public int? NumberOfPages { get; set; }

    public int? Revision { get; set; }

    public int? LatestRevision { get; set; }

    public string Language { get; set; }

    public DateTime? CreateDate { get; set; }

    public string Description { get; set; }

    public byte[] Image { get; set; }

    public int ClickCounter { get; set; }

    public  List<CommentDto> Comments { get; set; }

    public  List<UserBookDto> UserBooks { get; set; }

    public  List<AuthorDto> Authors { get; set; }

    public  List<CategoryDto> Categories { get; set; }

    public  List<LibraryDto> Libraries { get; set; }

这里有我的行动。

public IHttpActionResult GetAllForOfficer()
    {
        Library library = _libraryManager.GetById(ProviderAuthorization.libraryId);
        List<BookDto> booksDto = _bookManager.GetAll().Where(x => x.Libraries.Contains(library)).ToList().ConvertAll<BookDto>(x => new BookDto()
        {
            Id = x.Id,
            Name = x.Name,
            ISBN10 = x.ISBN10,
            ISBN13 = x.ISBN13,
            Authors = x.Authors.ToList().ConvertAll<AuthorDto>(y => new AuthorDto() { Id = y.Id, Name = y.Name }),
            Categories = x.Categories.ToList().ConvertAll<CategoryDto>(y => new CategoryDto() { Id = y.Id, Name = y.Name, ClickCounter = y.ClickCounter }),
            Comments = x.Comments.ToList().ConvertAll<CommentDto>(y => new CommentDto() { Id = y.Id, BookId = y.BookId, CommentString = y.Comment1, Date = y.Date, Status = y.Status, UserId = y.UserId }),
            Libraries = x.Libraries.ToList().ConvertAll<LibraryDto>(y => new LibraryDto() { Id = y.Id, Address = y.Address, Location = y.Location, Name = y.Name, Status = y.Status }),
            UserBooks = x.UserBooks.ToList().ConvertAll<UserBookDto>(y => new UserBookDto() { Id = y.Id, Status = y.Status, BookId = y.BookId, BorrowDate = y.BorrowDate, LibraryId = y.LibraryId, UserId = y.UserId, ReturnDate = y.ReturnDate }),
            ClickCounter = x.ClickCounter,
            CreateDate = x.CreateDate,
            Description = x.Description,
            Image = x.Image,
            Language = x.Language,
            LatestRevision = x.LatestRevision,
            NumberOfPages = x.NumberOfPages,
            Publisher = x.Publisher,
            Revision = x.Revision,
        });
        return Ok(booksDto);
    }

现在我有 Postman 输出就是这样。

{
    "Id": 1,
    "ISBN10": "123123",
    "ISBN13": "213123",
    "Name": "asdasd",
    "Publisher": "asdasd",
    "NumberOfPages": 2,
    "Revision": 2,
    "LatestRevision": 2,
    "Language": "asd",
    "CreateDate": "1010-10-10T00:00:00",
    "Description": "asdasd",
    "Image": null,
    "ClickCounter": 0,
    "Comments": [
        {
            "Id": 3,
            "UserId": 1,
            "BookId": 1,
            "CommentString": "TestComment",
            "Date": "2020-10-10T00:00:00",
            "Status": 1,
            "Book": null,
            "User": null
        }
    ],
    "UserBooks": [
        {
            "Id": 2,
            "UserId": 1,
            "BookId": 1,
            "LibraryId": 1,
            "BorrowDate": "1010-10-10T00:00:00",
            "ReturnDate": null,
            "Status": 1,
            "Book": null,
            "Library": null,
            "User": null
        }
    ],
    "Authors": [
        {
            "Id": 1,
            "Name": "TestAuthor1",
            "Books": null
        },
        {
            "Id": 2,
            "Name": "TestAuthor2",
            "Books": null
        }
    ],
    "Categories": [
        {
            "Id": 5,
            "Name": "TestCategory0",
            "ClickCounter": 0,
            "Books": null
        },
        {
            "Id": 6,
            "Name": "TestCategory1",
            "ClickCounter": 0,
            "Books": null
        }
    ],
    "Libraries": [
        {
            "Id": 1,
            "Name": "asdasd",
            "Address": "asdasd",
            "Location": "asdasd",
            "Status": 1,
            "Officers": null,
            "UserBooks": null,
            "Books": null,
            "Users": null
        },
        {
            "Id": 2,
            "Name": "asdasd",
            "Address": "asdasd",
            "Location": "asdasd",
            "Status": 2,
            "Officers": null,
            "UserBooks": null,
            "Books": null,
            "Users": null
        }
    ]
}

不使用 dto classes 也可以解决问题。那样

public IHttpActionResult GetAllForOfficer()
    {
        Library library = _libraryManager.GetById(ProviderAuthorization.libraryId);
        List<BookDto> booksDto = _bookManager.GetAll().Where(x => x.Libraries.Contains(library)).ToList().ConvertAll<BookDto>(x => new Book()
        {
            Id = x.Id,
            Name = x.Name,
            ISBN10 = x.ISBN10,
            ISBN13 = x.ISBN13,
            Authors = x.Authors.ToList().ConvertAll<Author>(y => new Author() { Id = y.Id, Name = y.Name }),
            Categories = x.Categories.ToList().ConvertAll<Category>(y => new Category() { Id = y.Id, Name = y.Name, ClickCounter = y.ClickCounter }),
            Comments = x.Comments.ToList().ConvertAll<Comment>(y => new Comment() { Id = y.Id, BookId = y.BookId, Comment1 = y.Comment1, Date = y.Date, Status = y.Status, UserId = y.UserId }),
            Libraries = x.Libraries.ToList().ConvertAll<Library>(y => new Library() { Id = y.Id, Address = y.Address, Location = y.Location, Name = y.Name, Status = y.Status }),
            UserBooks = x.UserBooks.ToList().ConvertAll<UserBook>(y => new UserBook() { Id = y.Id, Status = y.Status, BookId = y.BookId, BorrowDate = y.BorrowDate, LibraryId = y.LibraryId, UserId = y.UserId, ReturnDate = y.ReturnDate }),
            ClickCounter = x.ClickCounter,
            CreateDate = x.CreateDate,
            Description = x.Description,
            Image = x.Image,
            Language = x.Language,
            LatestRevision = x.LatestRevision,
            NumberOfPages = x.NumberOfPages,
            Publisher = x.Publisher,
            Revision = x.Revision,
        });
        return Ok(booksDto);
    }

但是在转换 ICollection 对象时出现问题。我将 ICollection 转换为列表以使用 ConvertAll 方法。但最后我不得不将其更改为ICollection。所以最后一个代码示例不起作用。也许有办法,但我没有搜索它。