Search code examples
c#asp.net-mvcentity-frameworkjson.net.net-framework-version

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


I have book class that includes authors and also my author class includes books. So when I try to convert book to json string, it gives the referance loop error. So I do that

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

And this returns output like that

"[\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                          

I have book class like this

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; }

My output looks a little bit weird. It should be like "Authors":["Name":"Pete Mark"]. But there are some backslashes. I think I have a problem with converting class to json. How can I fix that ?


Solution

  • Thanks everybody who says I dont actually need any conversion. I solved that problem using data transfer object. I created same classes. And convert it to dto by not equalizing child class's relationship objects (which is typed ICollection).

    Here is my book entity.

    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; }
    

    And here is 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; }
    

    And I have my action here.

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

    Now I have Postman output is like that.

    {
        "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
            }
        ]
    }
    

    Also the problem could be resolved without using dto classes. Like that

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

    But there is a problem while converting ICollection objects. I convert ICollection to list to use ConvertAll method. But in the end I have to change it to ICollection. So this last code sample is not working. Maybe there is a way but I didnt search about it.