在将Entity Framework对象图序列化为Json时防止StackOverflowException

时间:2012-02-05 16:35:58

标签: c#-4.0 entity-framework-4 self-tracking-entities servicestack jsonserializer

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

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

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

ERD

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

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

生成StackOverflowException。 我相信这是因为EF提供了一个无限模型,可以将串行器拧紧。也就是说,我可以称之为:profile.ProfileImages[0].Profile.ProfileImages[0].Profile ...等等。

如何“压扁”我的EF对象图或以其他方式阻止 ServiceStack.JsonSerializer 进入堆栈溢出情况?

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

3 个答案:

答案 0 :(得分:9)

您有相互矛盾的问题,EF模型已经过优化,可以将您的数据模型存储在RDBMS中,而不是用于序列化 - 这是具有单独DTO的角色。否则,您的客户将绑定到您的数据库,您的数据模型上的每个更改都有可能破坏您现有的服务客户端。

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

ServiceStack.Common包括内置的映射函数(即TranslateTo / PopulateFrom),它简化了将实体映射到DTO,反之亦然。这是一个显示这个的例子:

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

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

答案 1 :(得分:7)

为了我的同伴StackOverflowers进入这个问题,我将解释我最终做了什么:

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

顺便说一下,您仍然可以使用ServiceStack.JsonSerializer进行反序列化 - 它比.NET更快,而且您没有问过StackOverflowException问题。

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

  

说明:如果没有[ScriptIgnore],序列化(使用.NET Javascript序列化程序)也会引发异常,关于循环   引用(类似于引发StackOverflowException的问题   ServiceStack)。我们需要消除循环,这就完成了   使用[ScriptIgnore]

因此,我编辑了ADO.NET Self-Tracking Entity Generator Template附带的.TT文件,并将其设置为在相关位置包含[ScriptIgnore](如果有人想要代码差异,请写一条评论)。有人说编辑这些“外部”,不是意味着要编辑的文件是一种不好的做法,但是哎呀 - 它解决了这个问题,这是唯一不会迫使我重新构建整个问题的方法。应用(使用POCO而不是STE,将DTO用于所有等等。)

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

答案 2 :(得分:1)

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

我还使用了Newton JsonSerializer。

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