Preventing StackOverflowException while serializing Entity Framework object graph into Json
Asked Answered
H

3

4

I want to serialize an Entity Framework Self-Tracking Entities full object graph (parent + children in one to many relationships) into Json.

For serializing I use ServiceStack.JsonSerializer.

This is how my database looks like (for simplicity, I dropped all irrelevant fields):

ERD

I fetch a full profile graph in this way:

public Profile GetUserProfile(Guid userID)
{
    using (var db = new AcmeEntities())
    {
        return db.Profiles.Include("ProfileImages").Single(p => p.UserId == userId);
    }
}

The problem is that attempting to serialize it:

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

produces a StackOverflowException. I believe that this is because EF provides an infinite model that screws the serializer up. That is, I can techincally call: profile.ProfileImages[0].Profile.ProfileImages[0].Profile ... and so on.

How can I "flatten" my EF object graph or otherwise prevent ServiceStack.JsonSerializer from running into stack overflow situation?

Note: I don't want to project my object into an anonymous type (like these suggestions) because that would introduce a very long and hard-to-maintain fragment of code).

Heptarchy answered 5/2, 2012 at 16:35 Comment(0)
B
9

You have conflicting concerns, the EF model is optimized for storing your data model in an RDBMS, and not for serialization - which is what role having separate DTOs would play. Otherwise your clients will be binded to your Database where every change on your data model has the potential to break your existing service clients.

With that said, the right thing to do would be to maintain separate DTOs that you map to which defines the desired shape (aka wireformat) that you want the models to look like from the outside world.

ServiceStack.Common includes built-in mapping functions (i.e. TranslateTo/PopulateFrom) that simplifies mapping entities to DTOs and vice-versa. Here's an example showing this:

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

The alternative is to decorate the fields you want to serialize on your Data Model with [DataContract] / [DataMember] fields. Any properties not attributed with [DataMember] wont be serialized - so you would use this to hide the cyclical references which are causing the StackOverflowException.

Balneal answered 5/2, 2012 at 17:50 Comment(3)
I actually need the serialization for caching in Redis. I fetch the whole object graph (that is - entities) of a user which is their profile, images and more data in child objects via EF. Then I want to save it in Redis as Json (key: user:<userid>:profile), so next time the client needs information about the user, it will be served from the Redis cache. Building DTOs (for that purpose) will be virtually copying the whole set of entities generated by EF. I also cannot decorate the fields, because the entity classes are auto-generated by an STE generator.Heptarchy
The thing is that while trying to isolate the problem, or finding a workaround, I found that even creating a "hand made" Profile object with a child ProfileImage object and serializing it with ServiceStack.JsonSerializer, causes a StackOverflowException, but using System.Web.Script.Serialization.JavaScriptSerializer actually succeeds. I still have the problem of building these objects manually (populating them in EF and re-establish "cleaner" versions) but it works...Heptarchy
If I could just give you a medal for the decoration. Solved my problem marking entity classes as [DataContract] and any member I want serialized with [DataMember]Biparous
H
7

For the sake of my fellow StackOverflowers that get into this question, I'll explain what I eventually did:

In the case I described, you have to use the standard .NET serializer (rather than ServiceStack's): System.Web.Script.Serialization.JavaScriptSerializer. The reason is that you can decorate navigation properties you don't want the serializer to handle in a [ScriptIgnore] attribute.

By the way, you can still use ServiceStack.JsonSerializer for deserializing - it's faster than .NET's and you don't have the StackOverflowException issues I asked this question about.

The other problem is how to get the Self-Tracking Entities to decorate relevant navigation properties with [ScriptIgnore].

Explanation: Without [ScriptIgnore], serializing (using .NET Javascript serializer) will also raise an exception, about circular references (similar to the issue that raises StackOverflowException in ServiceStack). We need to eliminate the circularity, and this is done using [ScriptIgnore].

So I edited the .TT file that came with ADO.NET Self-Tracking Entity Generator Template and set it to contain [ScriptIgnore] in relevant places (if someone will want the code diff, write me a comment). Some say that it's a bad practice to edit these "external", not-meant-to-be-edited files, but heck - it solves the problem, and it's the only way that doesn't force me to re-architect my whole application (use POCOs instead of STEs, use DTOs for everything etc.)

@mythz: I don't absolutely agree with your argue about using DTOs - see me comments to your answer. I really appreciate your enormous efforts building ServiceStack (all of the modules!) and making it free to use and open-source. I just encourage you to either respect [ScriptIgnore] attribute in your text serializers or come up with an attribute of yours. Else, even if one actually can use DTOs, they can't add navigation properties from a child object back to a parent one because they'll get a StackOverflowException. I do mark your answer as "accepted" because after all, it helped me finding my way in this issue.

Heptarchy answered 8/2, 2012 at 20:38 Comment(3)
Sure you can go down this path - tho I expect it to be a source of future friction. This is what the role of Data Transfer Objects (DTOs) solves. Note: Had you used a Micro ORM that works off POCOs you could avoid this problem, since POCOs are very clean/persistent friendly.Balneal
At the time I started building the infrastructure of our application, I really wanted to use EF because of its strong roots, rather than trying Micro ORM frameworks. I don't make a point whether this was the best decision at the time, but I took it. And at that time, EF Code-First wasn't ready yet. Changing it all now to POCO and/or other ORM framework will be a real pain...Heptarchy
Note: it's ok to go with EF, (most of .NET has :) - just pointing out that it's doesn't promote the ideal models for serialization. You'll find very few serializers will be able to serialize it as expected - without lots of massaging.Balneal
S
1

Be sure to Detach entity from ObjectContext before Serializing it.

I also used Newton JsonSerializer.

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

Stipe answered 22/2, 2012 at 10:54 Comment(2)
Take a try with this json library for serialization, I also did not have succes with standard Serializers. Install JSON.NET through NuGET and create small demoStipe
ServiceStack did the job for me. Thanks.Heptarchy

© 2022 - 2024 — McMap. All rights reserved.