Search code examples
c#entity-framework.net-core.net-6.0system.text.json

Entity Framework : reference loop in many to many relationship


I have this three entities Customer, Product and Review.

A customer can have many products, and a product can have only one customer as owner. A customer can also have many reviews, and one review can have only one customer. A product can have many reviews.

It seems like I am having a reference loop and below is the JsonException that I get when trying to get all customers:

Error message

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: $.rows.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Product.Reviews.Id.

Code:

namespace Domain.Entities
{
    public partial class Customer
    {
        public int Id { get; set; }
        public string? Name { get; set; }
        public virtual ICollection<Review> Reviews { get; set; }
    }

    public partial class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public int Price { get; set; }
        public int CustomerId { get; set; }
        public Customer Customer { get; set; }
        public virtual ICollection<Review> Reviews { get; set; }
    }

    public partial class Review
    {
        public int Id { get; set; }
        public int Stars { get; set; }
        public string Description { get; set; }
        public int CustomerId { get; set; }
        public int ProductId { get; set; }
        public Customer Customer { get; set; }
        public Product Product { get; set; }
    }
}

ModelBuilder configurations:

// Products configurations 
builder.Ignore(e => e.DomainEvents);
builder.HasKey(t => t.Id);

// Customers configurations
builder.Ignore(e => e.DomainEvents);
builder.HasMany(e => e.Reviews)
       .WithOne(e => e.Customer)
       .HasForeignKey(uc => uc.Id);

builder.HasMany(e => e.MessagesSent)
       .WithOne(e => e.Receiver)
       .HasForeignKey(uc => uc.SenderId)
       .OnDelete(DeleteBehavior.Cascade);

builder.HasMany(e => e.MessagesReceived)
       .WithOne(e => e.Sender)
       .HasForeignKey(uc => uc.ReceiverId)
       .OnDelete(DeleteBehavior.Cascade);

// Reviews configurations
builder.HasKey(t => t.Id);
builder.HasOne(d => d.Customer)
        .WithMany(p => p.Reviews)
        .HasForeignKey(t => t.CustomerId)
        .OnDelete(DeleteBehavior.Cascade);

builder.HasOne(d => d.Product)
        .WithMany(p => p.Reviews)
        .HasForeignKey(t => t.ProductId)
        .OnDelete(DeleteBehavior.Cascade);

Any idea on how to fix this error?

Thanks in advance and if you need any more information please do let me know and I will provide asap.

Edit: this is the query that I am using for getting all customers:

public async Task<PaginatedData<CustomerDto>> Handle(CustomersWithPaginationQuery request)
{
    var filters = PredicateBuilder.FromFilter<Customer>("");
    var data = await _context.Customers
                             .Where(filters)
                             .OrderBy("Id desc")
                             .ProjectTo<CustomerDto>(_mapper.ConfigurationProvider)
                             .PaginatedDataAsync(1, 15);

    return data;
}

Edit #2: CustomerDto

namespace Application.Customers.DTOs
{
    public partial class CustomerDto : IMapFrom<Customer>
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public List<Review> Reviews { get; set; }
    }
}

Solution

  • To fix this issue you need to add a ReviewDto class like this:

    public partial class ReviewDto
        {
            public int Id { get; set; }
            public int Stars { get; set; }
            public string Description { get; set; }
           // ...
       
        }
    

    And update the CustomerDto:

     public partial class CustomerDto : IMapFrom<Customer>
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public List<ReviewDto> Reviews { get; set; }
        }