在将实体框架对象图序列化为 Json 时防止 ***Exception

Posted

技术标签:

【中文标题】在将实体框架对象图序列化为 Json 时防止 ***Exception【英文标题】:Preventing ***Exception while serializing Entity Framework object graph into Json 【发布时间】:2012-02-27 09:15:00 【问题描述】:

我想将Entity Framework Self-Tracking Entities 完整对象图(一对多关系中的父+子)序列化为Json。

对于序列化,我使用 ServiceStack.JsonSerializer

这就是我的数据库的样子(为简单起见,我删除了所有不相关的字段):

我以这种方式获取完整的个人资料图:

public Profile GetUserProfile(Guid userID)

    using (var db = new AcmeEntities())
    
        return db.Profiles.Include("ProfileImages").Single(p => p.UserId == userId);
    

问题在于尝试序列化它:

Profile profile = GetUserProfile(userId);
ServiceStack.JsonSerializer.SerializeToString(profile);

产生一个***Exception。 我相信这是因为 EF 提供了一个无限模型,将序列化器搞砸了。也就是说,我可以在技术上调用:profile.ProfileImages[0].Profile.ProfileImages[0].Profile ... 等等。

如何“展平”我的 EF 对象图或以其他方式防止 ServiceStack.JsonSerializer 遇到堆栈溢出情况?

注意: 我不想将我的对象投影到匿名类型(如 these suggestions),因为这会引入非常长且难以维护的代码片段)。

【问题讨论】:

【参考方案1】:

您有相互矛盾的顾虑,EF 模型针对将您的数据模型存储在 RDBMS 中进行了优化,而不是针对序列化 - 这就是具有单独 DTO 的作用。否则,您的客户端将绑定到您的数据库,您的数据模型的每一次更改都有可能破坏您现有的服务客户端。

话虽如此,正确的做法是维护您映射到的单独 DTO,这些 DTO 定义了您希望模型从外部世界看起来像的所需形状(也称为线格式)。

ServiceStack.Common 包含内置映射函数(即 TranslateTo/PopulateFrom),可简化实体到 DTO 的映射,反之亦然。下面是一个例子:

https://groups.google.com/d/msg/servicestack/BF-egdVm3M8/0DXLIeDoVJEJ

另一种方法是使用 [DataContract] / [DataMember] 字段装饰您想要在数据模型上序列化的字段。任何不属于 [DataMember] 的属性都不会被序列化 - 因此您可以使用它来隐藏导致 ***Exception 的循环引用。

【讨论】:

我实际上需要序列化以在 Redis 中进行缓存。我通过 EF 获取用户的整个对象图(即实体),即他们的个人资料、图像和子对象中的更多数据。然后我想将它保存在 Redis 中作为 Json (key: user::profile),这样下次客户端需要关于用户的信息时,它将从 Redis 缓存中提供。构建 DTO(为此目的)将虚拟复制 EF 生成的整个实体集。我也无法装饰字段,因为实体类是由 STE 生成器自动生成的。 问题是,在尝试隔离问题或寻找解决方法时,我发现即使使用子 ProfileImage 对象创建“手工制作”的 Profile 对象并使用 ServiceStack.JsonSerializer 对其进行序列化,也会导致***Exception,但使用 System.Web.Script.Serialization.javascriptSerializer 实际上成功了。我仍然有手动构建这些对象的问题(在 EF 中填充它们并重新建立“更干净”的版本),但它可以工作...... 如果我能给你一枚奖章作为装饰。解决了我将实体类标记为 [DataContract] 以及我想用 [DataMember] 序列化的任何成员的问题【参考方案2】:

为了解决这个问题的 ***ers 同胞,我将解释我最终做了什么:

在我描述的情况下,您必须使用标准的 .NET 序列化程序(而不是 ServiceStack 的):System.Web.Script.Serialization.JavaScriptSerializer。原因是您可以在 [ScriptIgnore] 属性中装饰您不希望序列化程序处理的导航属性。

顺便说一句,您仍然可以使用 ServiceStack.JsonSerializer 进行反序列化 - 它比 .NET 更快,并且您没有我问过这个问题的 ***Exception 问题。

另一个问题是如何让自跟踪实体用[ScriptIgnore]装饰相关的导航属性。

说明:没有[ScriptIgnore],序列化(使用.NET Javascript 序列化程序)也会引发异常,关于循环 参考资料(类似于在 服务栈)。我们需要消除循环,这就完成了 使用[ScriptIgnore]

所以我编辑了ADO.NET Self-Tracking Entity Generator Template 附带的.TT 文件,并将其设置为在相关位置包含[ScriptIgnore](如果有人想要代码差异,请给我写评论)。有人说编辑这些“外部”、不打算编辑的文件是一种不好的做法,但见鬼——它解决了问题,而且它是唯一不强迫我重新设计我的整体的方法应用程序(使用 POCO 代替 STE,对所有内容使用 DTO 等)

@mythz:我不完全同意你关于使用 DTO 的争论——请看我对你的回答的 cmets。我非常感谢您为构建 ServiceStack(所有模块!)并使其免费使用和开源所做的巨大努力。我只是鼓励您尊重文本序列化程序中的 [ScriptIgnore] 属性,或者提出您的属性。否则,即使实际上可以使用 DTO,他们也无法将导航属性从子对象添加回父对象,因为它们会收到 ***Exception。 我确实将您的回答标记为“已接受”,因为毕竟它帮助我找到了解决这个问题的方法。

【讨论】:

当然你可以走这条路——尽管我希望它会成为未来摩擦的根源。这就是数据传输对象 (DTO) 所要解决的问题。注意:如果你使用了一个对 POCO 有效的 Micro ORM,你可以避免这个问题,因为 POCO 非常干净/持久友好。 在我开始构建我们应用程序的基础架构时,我真的很想使用 EF,因为它的根基深厚,而不是尝试 Micro ORM 框架。我没有说明这是否是当时最好的决定,但我接受了。那时,EF Code-First 还没有准备好。现在将其全部更改为 POCO 和/或其他 ORM 框架将是一个真正的痛苦...... 注意:可以使用 EF,(大多数 .NET 都有 :) - 只是指出它不会促进理想的序列化模型。您会发现很少有序列化程序能够按预期序列化它 - 无需大量按摩。【参考方案3】:

确保在序列化之前从 ObjectContext 中分离实体。

我还使用了 Newton JsonSerializer。

JsonConvert.SerializeObject(EntityObject, Formatting.Indented, new JsonSerializerSettings PreserveReferencesHandling = PreserveReferencesHandling.Objects );

【讨论】:

尝试使用这个 json 库进行序列化,我也没有成功使用标准序列化器。通过 NuGET 安装 JSON.NET 并创建小演示 ServiceStack 为我完成了这项工作。谢谢。

以上是关于在将实体框架对象图序列化为 Json 时防止 ***Exception的主要内容,如果未能解决你的问题,请参考以下文章

在将 json 反序列化为对象时,使用 jackson 将 asp.net / MS 专有 json Dateformat 转换为 java8 LocalDateTime

通过 Web 传输实体框架对象并通过 JSON 返回的最佳方式

在将 JSON 反序列化为 C# 对象之前检查属性值

将 Json 反序列化为实体框架无法将 int 转换为类型

如何从实体序列化为json?

如何将具有嵌套属性的 JSON 对象反序列化为 Symfony 实体?