简体   繁体   中英

How to resolve System.Text.Json.JsonException: A possible object cycle was detected in Entity Framework?

I have a database with tables/data, so I have used the db first approach using the following command to scaffold the Models:

dotnet ef dbcontext scaffold "Server=.;Database=MyDb;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models

This has generated the DbContext and couple of model classes corresponding to my tables. This question involves only 2 models so I am providing the code below:

Person.cs:

using System;
using System.Collections.Generic;

namespace MyApp.Models
{
    public partial class Person
    {
        public Person()
        {
            Profiles= new HashSet<Profile>();
        }

        public int Id { get; set; }
        public string Email { get; set; } = null!;
        public string Password { get; set; } = null!;

        public virtual ICollection<Profile> Profiles { get; set; }
    }
}

Profile.cs:

using System;
using System.Collections.Generic;

namespace MyApp.Models
{
    public partial class Profile
    {
        public Profile()
        {
            Sales = new HashSet<Sale>();
        }

        public int Id { get; set; }
        public int PersonId { get; set; }
        public string ProfileName { get; set; } = null!;

        public virtual Person Person { get; set; } = null!;
        public virtual ICollection<Sale> Sales { get; set; }
    }
}

In the controller I am trying the following query:

var person = await _context.Person
                           .AsNoTracking()
                           .Where(c => c.Email == 'test@testmail.com')
                           .ToListAsync();

This works. But the following query throws an error:

var profiles = await _context.Person
                             .AsNoTracking()
                             .Where(c => c.Email == 'test@testmail.com')
                             .Include(c => c.Profiles)
                             .ToListAsync();

Error:

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:
$.Profiles.Person.Profiles.Person.Profiles.PersonProfiles.PersonProfiles.PersonProfiles.PersonProfiles.PersonProfiles.PersonProfiles.Person.Id.

at System.Text.Json.ThrowHelper.ThrowJsonException_SerializerCycleDetected(Int32 maxDepth)
at System.Text.Json.Serialization.JsonConverter 1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo 1.GetMemberAndWriteJson(Object obj, WriteStack& state, Utf8JsonWriter writer)

The reason you're getting this is because you're serializing an object graph that has a cycle, and this is typical of an EF DB entity graph

Imagine you have a Person with one Profile, and that Profile has a Person (a link back to the parent Person)

It is possible to write C# code like:

myPerson.Profiles.First().Person.Profiles.First().Person.Profiles.First().Person.Profiles.First().Person.Profiles.First().Person..

You can go on like that forever..

..and that is what the json serializer is also doing when it is trying to serialize every property of every profile

You have a few options..

  • You can modify the graph before you serialize it: set the Person of every Profile (or whatever level N property is the first property to link back to an earlier Nx one) to null to stop the cycle

  • You can serialize an object graph that doesn't have cycles, like a set of DTOs you map your EF entities onto before you serialize

  • You can tell Newtonsoft to watch the references and not set anything it saw before:

string json = JsonConvert.SerializeObject(joe, Formatting.Indented, new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});

Most people would, I say, avoid ever seeing this because they do option 2; generally we don't serialize our DB entities, we have a set of classes that are specifically designed for our front end to use, they might look different etc; it's rare for a system's front end to fully and exactly need data objects that look exactly like what the DB is modelling. If those front end focused objects don't have cycles (a person might have a list of profiles but there isn't any need for the profile to link back, because it's used too down as the hi is drawn) they don't cause this problem.

Be careful with option 1; if you call save on your context (for any reason) after you modified the graph to be serializer friendly, you'll could end up disconnecting relationships in the db. 3 is a bit of a kludge, a work around for the problem caused by not having done 2, but 2 is more effort to go to

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM