Entity Framework and JSON.NET
Converting code-first, EF6 Entity models to JSON (with JSON.NET) is more complicated than I expected. First of all, Include()
does not behave as I expected (after one level as suggested in “EF 6” or “.include with multiple levels”).
For me, there was no difference between this:
var segments = GetContext().Segments
.Include(i => i.ChildSegments)
.Where(i => (i.ParentSegmentId == null) && i.IsActive.HasValue && i.IsActive.Value)
.OrderBy(i => i.SegmentName);
…and this:
var segments = GetContext().Segments
.Include(i => i.ChildSegments)
.Include(i => i.ChildSegments)
.Where(i => (i.ParentSegmentId == null) && i.IsActive.HasValue && i.IsActive.Value)
.OrderBy(i => i.SegmentName);
I did not bother run SQL Profiler and dissect the SQL but in both cases there were no “grandchild” segments. (By the way, it’s been a few years so I should help to mention that EF6 over SQL Server still does not support the Nullable extension method GetValueOrDefault()
. So, for the example above, we see i.IsActive.HasValue && i.IsActive.Value
in place of i.IsActive.GetValueOrDefault()
.)
So my need for these “grandchild” segments suggests (correctly) that my Segment
type has a “parent” Segment
. This “self-join” can cause JSON.NET to throw a circular-reference exception and/or an out-of-memory exception (as it travels from parent to children—and children of children). Moreover, the Segment
has a Documents collection (where each Document
has a Segment
—faithfully duplicated by JSON.NET until it runs out of memory!) To address these issues I have this:
var documentSettings = new JsonSerializerSettings
{
ContractResolver = new InterfaceContractResolver<IDocument>(),
PreserveReferencesHandling = PreserveReferencesHandling.None,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
What stands out is my InterfaceContractResolver<IDocument>
, taking the guidance from Newton-King’s “Serialization using ContractResolver.” I’ve defined this resolver to ‘filter’ my Entity model Document through IDocument
(which defines no parent-child relations):
public class InterfaceContractResolver<TInterface> : DefaultContractResolver where TInterface : class
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(typeof(TInterface), memberSerialization);
return properties;
}
}
This looks straight forward when you want to do something like this:
var rootDocuments = segment.ChildSegments.Select(k =>
{
var rootDocument = context.Documents
.Where(l => l.SegmentId == k.SegmentId)
.Where(l => l.IsActive.HasValue && l.IsActive.Value)
.Where(l => l.IsRoot.HasValue && l.IsRoot.Value)
.FirstOrDefault();
var documentJson = JsonConvert.SerializeObject(rootDocument, documentSettings);
return documentJson;
})
.ToArray();
But what I have here is an array of JSON strings—why did I do that? Well, it turns out that I cannot (it could just be me) configure JSON.NET to handle enumerationOfDocuments
in this:
var json = JsonConvert.SerializeObject(new
{
SegmentId = segment.SegmentId,
SegmentName = segment.SegmentName,
SortOrdinal = segment.SortOrdinal,
CreateDate = segment.CreateDate,
ParentSegmentId = segment.ParentSegmentId,
ClientId = segment.ClientId,
IsActive = segment.IsActive,
ChildDocuments = enumerationOfDocuments,
});
Either it is not possible or I do not know how to extend DefaultContractResolver
to deal with an object that has an IEnumerable<Document>
property that should be ‘filtered’ into IEnumerable<IDocument>
.
So I’m resorting to old-fashioned string manipulation tricks to get around this limitation of me—or of JSON.NET.