How to get around the Circular Reference issue with JSON and Entity

asp.net-mvcentity-frameworkjsonorm

I have been experimenting with creating a website that leverages MVC with JSON for my presentation layer and Entity framework for data model/database. My Issue comes into play with serializing my Model objects into JSON.

I am using the code first method to create my database. When doing the code first method a one to many relationship (parent/child) requires the child to have a reference back to the parent. (Example code my be a typo but you get the picture)

class parent
{
   public List<child> Children{get;set;}
   public int Id{get;set;}

}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId")]
    public parent MyParent{get;set;}
    public string name{get;set;}
 }

When returning a "parent" object via a JsonResult a circular reference error is thrown because "child" has a property of class parent.

I have tried the ScriptIgnore attribute but I lose the ability to look at the child objects. I will need to display information in a parent child view at some point.

I have tried to make base classes for both parent and child that do not have a circular reference. Unfortunately when I attempt to send the baseParent and baseChild these are read by the JSON Parser as their derived classes (I am pretty sure this concept is escaping me).

Base.baseParent basep = (Base.baseParent)parent;
return Json(basep, JsonRequestBehavior.AllowGet);

The one solution I have come up with is to create "View" Models. I create simple versions of the database models that do not include the reference to the parent class. These view models each have method to return the Database Version and a constructor that takes the database model as a parameter (viewmodel.name = databasemodel.name). This method seems forced although it works.

NOTE:I am posting here because I think this is more discussion worthy. I could leverage a different design pattern to over come this issue or it could be as simple as using a different attribute on my model. In my searching I have not seen a good method to overcome this problem.

My end goal would be to have a nice MVC application that heavily leverages JSON for communicating with the server and displaying data. While maintaining a consistant model across layers (or as best as I can come up with).

Best Answer

I see two distinct subjects in your question:

  • How to manage circular references when serializing to JSON?
  • How safe is it to use EF entities as model entities in you views?

Concerning circular references I'm sorry to say that there is no simple solution. First because JSON cannot be used to represent circular references, the following code:

var aParent = {Children : []}, aChild  = {Parent : aParent};
aParent.Children.push(aChild);
JSON.stringify(aParent);

Results in: TypeError: Converting circular structure to JSON

The only choice you have is to keep only the composite --> component part of the composition and discard the "back navigation" component --> composite, thus in you example:

class parent
{
    public List<child> Children{get;set;}
    public int Id{get;set;}
}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId"), ScriptIgnore]
    public parent MyParent{get;set;}
    public string name{get;set;}
}

Nothing prevents you from recomposing this navigation property on your client side, here using jQuery:

$.each(parent.Children, function(i, child) {
  child.Parent = parent;  
})

But then you will need to discard it again before sending it back to the server, for JSON.stringify won't be able to serialize the circular reference:

$.each(parent.Children, function(i, child) {
  delete child.Parent;  
})

Now there is the issue of using EF entities as your view model entities.

First EF is likely to using Dynamic Proxies of your class to implement behaviors such as change detection or lazy loading, you have to disable those if you want to serialize the EF entities.

Moreover using EF entities in the UI can be at risk since all the default binder will mapping every field from the request to entities fields including those you did not want the user to set.

Thus, if you want you MVC app to be properly designed I would recommended to use a dedicated view model to prevent the "guts" of your internal business model from being exposed to the client, thus I would recommend you a specific view model.