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

Time:01-05

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 ?

CodePudding user response:

you don' t need any special conversion, net will do it for you. If you try to make json looking prety, it doesn' t make any sense on the server side. You will have to deserialiaze or parse it on the client side after this.

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

CodePudding user response:

A quick way to solve your problem would be to add the [JsonIgnore] attribute to your Authors, Categories and Libraries properties in the Book class (and potentially some other attributes in your other classes aswell).

A better way would be to write custom JsonConverters for those properties. For example, the Book class could use a AuthorNameConverter which converts an Author object into a string with the author's name when serializing. You can add that converter as an attribute of your Authors property, or add it in the available converters of your JsonSerializerSettings.

You can find an example of a JsonConverter here : https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm

For adding it as an attribute :

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

CodePudding user response:

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.

  •  Tags:  
  • Related