简体   繁体   English

Newtonsoft Json 序列化和反序列化问题(检测到属性的自引用循环)

[英]Newtonsoft Json Serialization and Deserialization Issue (Self referencing loop detected for property)

I have a many to many Relation between Course and Student Entities some Weird Behavior Happens When i try to Get Courses for the options我在课程和学生实体之间有多对多的关系当我尝试获取选项的课程时会发生一些奇怪的行为

  • in Edit Screen, I Get the Self referencing loop detected for property Course在编辑屏幕中,我为属性课程检测到自引用循环
  • In Add Screen, it works Fine With No Issue在添加屏幕中,它可以正常工作,没有问题

I tried Looking For the cause but did not come up with anything useful.我尝试寻找原因,但没有想出任何有用的东西。

the Relation is:- 1- Course can have Many Students 2- Student can have many course关系是:- 1- 课程可以有很多学生 2- 学生可以有很多课程

The tables are 1- Course 2- StudentCourse (With CourseId as Foreign Key, StudentId as Foreign Key) 3- Student这些表是 1- 课程 2- StudentCourse(以 CourseId 作为外键,StudentId 作为外键) 3- Student

please note that the person table has the same exact scenario but, I do not face the Same issue There请注意,人员表具有相同的确切情况,但是我没有遇到相同的问题

    public class Repository<TEntity>:IRepository<TEntity> where TEntity : class
    {
        protected readonly DbContext Context;

        public Repository(DbContext context)
        {
            Context = context;
        }
        public async Task<List<T>> GetAllAsync<T>(Expression<Func<TEntity, bool>> predicate)
        {
            var q = Context.Set<TEntity>().Where(predicate);
            var ret = await q.ToListAsync();
            List<T> result = JsonConvert.DeserializeObject<List<T>>(JsonConvert.SerializeObject(ret));
            return result;
        }
   }

the Line Causing The Exception导致异常的行

List<T> result = JsonConvert.DeserializeObject<List<T>>(JsonConvert.SerializeObject(ret));

Here Is the Stack Trace.这是堆栈跟踪。

at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CheckForCircularReference(JsonWriter writer, Object value, JsonProperty property, JsonContract contract, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer, Object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, JsonContract& memberContract, Object& memberValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonConvert.SerializeObjectInternal(Object value, Type type, JsonSerializer jsonSerializer)
   at Newtonsoft.Json.JsonConvert.SerializeObject(Object value, Type type, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.SerializeObject(Object value)
   at ApheliateProgram.Data.DataAccessLayer.Repositories.Repository`1.<GetAllAsync>d__6`1.MoveNext()

My advice to avoid pain like this in the present and the future, do away with the Generic Repository.我的建议是在现在和将来避免这样的痛苦,取消通用存储库。

This method looks to be an attempt to use lazy loading to fetch an entire object graph in a generic way using a serializer to do a deep copy, or you are stumbling on a side-effect of either a lazy load serializer behaviour or happen to have some related entities already pre-fetched by the DbContext and that is tripping up your serialization.此方法看起来是尝试使用延迟加载来以通用方式获取整个 object 图形,使用序列化程序进行深度复制,或者您遇到延迟加载序列化程序行为的副作用或碰巧有DbContext 已经预取了一些相关实体,这会影响您的序列化。

You can avoid all manner of issues like this by leveraging projection to populate a view model for consumption.您可以通过利用投影来填充视图 model 以供使用,从而避免各种此类问题。 However this is not a Generic compatible operation, but it keeps the code simple and fast.然而,这不是通用兼容操作,但它使代码保持简单和快速。

Where I use repositories I have them pass back IQueryable<TEntity> rather than IEnumerable<TEntity> or Task<IEnumerable<TEntity>> as this gives the callers full control over how the data is consumed including projection, pagination, sorting, additional filtering, as well as whether the call should be asynchronous or synchronous.在我使用存储库的地方,我让它们传回IQueryable<TEntity>而不是IEnumerable<TEntity>Task<IEnumerable<TEntity>>因为这使调用者可以完全控制数据的使用方式,包括投影、分页、排序、附加过滤、以及调用应该是异步的还是同步的。 The same goes for calls where I do want to work with entities (such as doing Updates) the caller control over whether and what related data is eager loaded.对于我确实想使用实体(例如进行更新)的调用也是如此,调用者控制是否以及哪些相关数据被急切加载。

To help explain the issue you are encountering with the Generic implementation:为了帮助解释您在使用通用实现时遇到的问题:

By loading Student data using what amounts to _context.Set<Student>() that can fetch a Student record.通过使用可以获取学生记录的_context.Set<Student>()加载学生数据。 However a Student record contains references to Courses, and Courses contain references back to Student.但是,学生记录包含对课程的引用,而课程包含对学生的引用。 (Including the record for this student which amounts to a circular reference) (包括该学生的记录,相当于循环引用)

With lazy loading enabled, the serializer is going to "touch" Courses and proceed to fetch all related courses.启用延迟加载后,序列化程序将“触摸”课程并继续获取所有相关课程。 Then as it goes through each course, it is going to "touch" the Students for that course, then for each Student, touch courses... You can limit the depth, but also have to account for any circular references.然后当它通过每门课程时,它将“接触”该课程的学生,然后对于每个学生,接触课程......您可以限制深度,但也必须考虑任何循环引用。 Even without running into errors this gets extremely expensive as each "touch" results in another query getting run against the database.即使没有遇到错误,这也会变得非常昂贵,因为每次“触摸”都会导致对数据库运行另一个查询。

Even with lazy loading disabled, when you fetch a Student, the DbContext will look through any courses it might happen to be tracking and it will automatically add references to those courses in the Student when it is returned.即使禁用了延迟加载,当您获取 Student 时,DbContext 也会查看它可能正在跟踪的所有课程,并在返回时自动在 Student 中添加对这些课程的引用。 Where you have a reference to a Course that refers back to this same student, the serializer can trigger an exception on finding the circular reference.如果您对某个 Course 的引用又引用了该学生,则序列化程序可以在查找循环引用时触发异常。 This also leads to incomplete and unpredictable related data being sent to your view / consumer as the DbContext will fill in anything it knows about which might be all related data, some related data, or no related data.这也会导致不完整和不可预测的相关数据被发送到您的视图/消费者,因为 DbContext 将填写它所知道的任何内容,可能是所有相关数据、一些相关数据或没有相关数据。

Instead, if I have a StudentRepository that returns IQueryable<Student> you get something like this:相反,如果我有一个返回IQueryable<Student>的 StudentRepository,你会得到如下内容:

public class StudentRepository
{
    public IQueryable<Student> GetStudents()
    {
        var query = Context.Students.AsQueryable();
        // or could use:
        //var query = Context.Set<Student>().AsQueryable();
        return query;
    }
}

You don't need the predicate, as the caller is composing that anyways.您不需要谓词,因为调用者无论如何都在编写它。 What filtering the repository can do is low level rules that you want to ensure are done consistently, such as if you have a soft-delete model (Ie IsActive):过滤存储库可以做的是您希望确保始终如一地完成的低级规则,例如,如果您有软删除 model(即 IsActive):

    public IQueryable<Student> GetStudents(bool includeInactive = false)
    {
        var query = Context.Students.AsQueryable();
        if(!includeInactive)
            query = query.Where(x => x.IsActive);

        return query;
    }

This ensures that by default only active students are ever returned.这确保了默认情况下只返回活跃的学生。 The same can be used to apply ownership checks against the current user to ensure the data returned is only that which they are allowed to see.同样可以用于对当前用户应用所有权检查,以确保返回的数据仅是他们被允许查看的数据。 (Such as in the case of a multi-tenant SaaS system) (例如在多租户 SaaS 系统的情况下)

The caller that wants the student data:想要学生数据的调用者:

var students = StudentRepository.GetStudents()
    .Where(x => {insert where conditions})
    .OrderBy(...)
    ....

From here we can Skip , Take , ToList , Any , Count , etc. as well as using Include to eager load data if we want to work with the entities themselves.从这里我们可以SkipTakeToListAnyCount等,如果我们想使用实体本身,也可以使用Include来预先加载数据。 All of this adds considerable complexity to support with IEnumerable .所有这些都为IEnumerable的支持增加了相当大的复杂性。 I can call synchronous methods like ToList() or await asynchronous calls without doubling my effort in the repository or forcing everything to use one or the other.我可以调用ToList()之类的同步方法或等待异步调用,而无需在存储库中加倍努力或强制所有内容使用其中一个或另一个。

By using IQueryable we are not "leaking" domain knowledge any more than using a more restrictive Generic Repository pattern because the act of composing details like that predicate requires knowledge of the domain and EF-isms as the passed predicate must conform to what EF can work with.通过使用IQueryable ,我们不会“泄漏”领域知识,而不是使用更具限制性的通用存储库模式,因为像谓词这样组合细节的行为需要领域知识和 EF 主义,因为传递的谓词必须符合 EF 可以工作的内容和。 (No calling methods, accessing non-mapped properties, etc.) (没有调用方法,访问非映射属性等)

From there you can leverage AutoMapper's ProjectTo to project the IQueryable result to a ViewModel or DTO containing just the data the consumer needs that can be safely serialized without worrying about circular references or triggering lazy loads.从那里,您可以利用 AutoMapper 的ProjectTo将 IQueryable 结果投影到 ViewModel 或 DTO,其中仅包含消费者需要的数据,这些数据可以安全地序列化,而无需担心循环引用或触发延迟加载。 Alternatively the projection can be done manually with Select .或者,可以使用Select手动完成投影。 It avoids a lot of issues and provides a significant performance and resource utilization improvement as well.它避免了很多问题,并提供了显着的性能和资源利用率改进。

Adding ReferenceLoopHandling.Ignore option Seems to fix the problem.添加ReferenceLoopHandling.Ignore选项似乎可以解决问题。

JsonConvert.SerializeObject(ert, new JsonSerializerSettings{ ReferenceLoopHandling = ReferenceLoopHandling.Ignore})

thanks @Guru Stron谢谢@Guru Stron

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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