user1574598
user1574598

Reputation: 3881

Better Way of Dealing With Circular References? - EF Core 3.1 and Web API

I am currently trying to progress with EF Core with a one-to-many (a user has many items). A tutorial or three later I managed to get things working with two very small and simple tables; however, I got a json exception: A possible object cycle was detected which is not supported which indicated that I had circular references.

Here is my code that gets around the issue using DTO objects, but is there a more cleaner way I can get around this issue as typing the, though it works, felt a bit wrong.

User:

namespace TestWebApplication.Database
{
    public class User
    {
        [Key]
        public int UserId { get; set; }
        public string UserName { get; set; }
        public string Dob { get; set; }
        public string Location { get; set; }
        public ICollection<Items> Items { get; set; }
    }
} 

Items:

namespace TestWebApplication.Database
{
    public class Items
    {
        [Key]
        public int ItemId { get; set; }
        public string Item { get; set; }
        public string Category { get; set; }
        public string Type { get; set; }
        public virtual User User { get; set; }
    }
}

DtoItems:

namespace TestWebApplication.Database.DTOs
{
    public class DtoItems
    {
        public string Item { get; set; }
        public string Category { get; set; }
        public string Type { get; set; }
        public DtoUser User { get; set; }
    }
}

DtoUser:

namespace TestWebApplication.Database.DTOs
{
    public class DtoUser
    {
        public string UserName { get; set; }
        public string Dob { get; set; }
        public string Location { get; set; }
    }
}

TestController:

[HttpGet]
[Route("getitems")]
public ActionResult<List<Items>> GetItems()
{
    List<Items> items = _myContext.Items.Include(i => i.User).ToList();

    // DTOs
    List<DtoItems> dtoItems = new List<DtoItems>();

    foreach (var i in items)
    {
        var dtoItem = new DtoItems
        {
            Item = i.Item,
            Category = i.Category,
            Type = i.Type,
            User = new DtoUser
            {
                UserName = i.User.UserName,
                Dob = i.User.Dob,
                Location = i.User.Location
            }
        };

        dtoItems.Add(dtoItem);
    }

    return Ok(dtoItems);
}

The output from endpoint:

[
    {
        "item": "xxx",
        "category": "xxx",
        "type": "xxx",
        "user": {
            "userName": "xxx",
            "dob": "xxx",
            "location": "xx"
        }
    },
    {
        "item": "xxx",
        "category": "xxx",
        "type": "xxx",
        "user": {
            "userName": "xxx",
            "dob": "xxx",
            "location": "xxx"
        }
    }
]

Upvotes: 3

Views: 4073

Answers (1)

MindingData
MindingData

Reputation: 12470

In my opinion, the use of a DTO is the correct way of dealing with this issue. The fact that your datamodel does not serialize well is trying to hint to you that you should not be serializing your datamodel from the API at all.

I think returning a DTO also solves further issues down the road (What if you want to return all properties of the UserModel except one, maybe it's a sensitive property you don't want to just return from your API, what if your UserModel in the DB gets more navigation properties that you don't want to return?).

There is really only two other ways of handling this.

  • You can switch to Newtonsoft.Json which has support for handling reference loops and you can configure it one single line

Like so :

services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);

System.Text.Json does not have support for doing this (yet). Follow this Github Issue for more info : https://github.com/dotnet/runtime/issues/30820

  • You use the JsonIgnore attribute to force non serialization of properties which will work but... It looks weird to have an EntityFramework model have JSON Serialization options on it...

So your best bet, stick with the DTO.

More info :

Upvotes: 7

Related Questions