【问题标题】:Newtonsoft Json Serialization and Deserialization Issue (Self referencing loop detected for property)Newtonsoft Json 序列化和反序列化问题(检测到属性的自引用循环)
【发布时间】:2021-12-23 21:31:52
【问题描述】:

我在课程和学生实体之间存在多对多关系 当我尝试获取选项的课程时会发生一些奇怪的行为

  • 在编辑屏幕中,我为属性 Course 检测到自引用循环
  • 在添加屏幕中,它可以正常工作,没有问题

我尝试寻找原因,但没有找到任何有用的东西。

关系是:- 1-课程可以有很多学生 2-学生可以有很多课程

这些表是 1- 课程 2- StudentCourse(以 CourseId 作为外键,StudentId 作为外键) 3- 学生

请注意,人员表具有相同的确切情况,但是我没有遇到相同的问题

    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;
        }
   }

导致异常的行

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

这是堆栈跟踪。

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()

【问题讨论】:

标签: c# entity-framework .net-core json.net ef-core-5.0


【解决方案1】:

我的建议是在现在和将来避免这样的痛苦,取消通用存储库。

此方法看起来是尝试使用延迟加载以通用方式获取整个对象图,使用序列化程序进行深度复制,或者您遇到了延迟加载序列化程序行为的副作用或碰巧有一些相关实体已经被 DbContext 预取,这会影响您的序列化。

您可以通过利用投影来填充视图模型以供使用,从而避免此类问题。然而,这不是通用兼容操作,但它使代码保持简单和快速。

在我使用存储库的地方,我让它们返回 IQueryable&lt;TEntity&gt; 而不是 IEnumerable&lt;TEntity&gt;Task&lt;IEnumerable&lt;TEntity&gt;&gt;,因为这使调用者可以完全控制数据的使用方式,包括投影、分页、排序、附加过滤以及调用应该是异步的还是同步的。对于我确实想要处理实体(例如进行更新)的调用也是如此,调用者控制是否以及哪些相关数据被急切加载。

为了帮助解释您在使用通用实现时遇到的问题:

通过使用可以获取学生记录的 _context.Set&lt;Student&gt;() 来加载学生数据。但是,学生记录包含对课程的引用,而课程包含对学生的引用。 (包括该学生的记录,相当于循环引用)

启用延迟加载后,序列化程序将“触摸”课程并继续获取所有相关课程。然后当它通过每门课程时,它将“接触”该课程的学生,然后对于每个学生,接触课程......您可以限制深度,但也必须考虑任何循环引用。即使没有遇到错误,这也会变得非常昂贵,因为每次“触摸”都会导致对数据库运行另一个查询。

即使禁用延迟加载,当您获取 Student 时,DbContext 也会查看它可能正在跟踪的所有课程,并在返回时自动在 Student 中添加对这些课程的引用。如果您对某个 Course 的引用又引用了该学生,则序列化程序可以在查找循环引用时触发异常。这也会导致不完整且不可预测的相关数据被发送到您的视图/消费者,因为 DbContext 将填写它所知道的任何内容,可能是所有相关数据、一些相关数据或没有相关数据。

相反,如果我有一个返回 IQueryable&lt;Student&gt; 的 StudentRepository,你会得到这样的结果:

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

您不需要谓词,因为无论如何调用者都在编写它。 过滤存储库可以做的是您希望确保始终如一地执行的低级规则,例如,如果您有一个软删除模型(即 IsActive):

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

        return query;
    }

这可确保默认情况下只返回活跃的学生。同样可以用于对当前用户应用所有权检查,以确保返回的数据仅是他们被允许查看的数据。 (例如在多租户 SaaS 系统的情况下)

想要学生数据的调用者:

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

从这里我们可以SkipTakeToListAnyCount 等,如果我们想使用实体本身,也可以使用Include 预加载数据。所有这些都为IEnumerable 的支持增加了相当大的复杂性。我可以调用ToList() 之类的同步方法或等待异步调用,而无需在存储库中加倍努力或强制所有内容使用其中一个。

通过使用IQueryable,我们不会“泄露”领域知识,而是使用限制性更强的通用存储库模式,因为像该谓词这样组合细节的行为需要领域知识和 EF-isms 知识,因为传递的谓词必须符合EF 可以使用什么。 (无调用方法,访问非映射属性等)

从那里您可以利用 AutoMapper 的 ProjectTo 将 IQueryable 结果投影到仅包含消费者需要的数据的 ViewModel 或 DTO,这些数据可以安全地序列化,而无需担心循环引用或触发延迟加载。或者,可以使用Select 手动完成投影。它避免了很多问题,并显着提高了性能和资源利用率。

【讨论】:

    【解决方案2】:

    添加ReferenceLoopHandling.Ignore 选项似乎可以解决问题。

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

    感谢@Guru Stron

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多