Friday, 12 October 2012

Working with Entity Framework as JSON Objects


Few months back, Henrik Nielsen wrote a great post on how to return JSON from ASP.NET Web API using JSON.NET on the msdn blog.

With Entity Framework rapidly becoming a choice of ORM framework for most enterprise scale applications (I also have my reservations using NHibernate for enterprise development, but let's just focus on EF for now), I’ve run into issues with serializing Entity Framework objects to JSON  using JSON.NET, from the ApiController over WCF.

After a bit of digging and delving, I discovered that the problem in this case lies in the lazy loading with EF, which causes circular reference between the objects. 

(e.g. “Self referencing loop detected for type (…)” when using JSON.NET and "A circular reference was detected while serializing an object of type (…)” when using JavaScriptSerializer,  and so on and so forth)


The fundamental problem is the same though and here is a look at the workaround I made to get my EF objects serialized to JSON objects.

Let's try to reproduce the Self-referencing loop/error first.

We begin by defining two models:


public class Url
 {
     public int UrlId { get; set; }
     public string Address { get; set; }
     public string Description { get; set; }
     public string Uid { get; set; }
     public virtual List<Tag> Tags { get; set; }
 }
 public class Tag
 {
     public int TagId { get; set; }
     public string Name { get; set; }
 }


...and here is the DBContext:


public class UrlzipContext : DbContext
 {
    public DbSet<Url> Urls { get; set; }
    public DbSet<Tag> Tags { get; set; }
 }

Let's look at the ApiController Code:


public class ValuesController : ApiController
  {
  DbContext db = new DbContext();
  // GET /api/values
  public List<Url> Get()
  {
   var urls = db.Urls.Include("Tags").ToList();
   return urls;
  }
}


Trying to return a simple Url from the ApiController, we’d get the circular reference error as explained above ( which internally stems from the underlying serializer class being used, JSON.NET in this case )

Here is the pic depicting the error:





And this is where the solution we could use steps in.

Instead of letting the default serializer use the complete Domain Model Object, we try to pass to the serializer only a selected set of values, in the form of an object which is different from the Domain Model object.

Here is the mod applied to the LINQ query in the ApiController:
[Why are domain models exposed to views anyways?]


var urls = db.Urls.Include("Tags").Select(i => 

      new { i.Uid, i.UrlId, i.Address, i.Description, i.Tags});


Hold on... this isn't over yet :-)

The modded code won't yet compile yet as our old Get() method returns a List, whereas in here we are dealing with IQueryable of Anonymous Type.

Unfortunately there is no simple way to return IQueryable of Anonymous Type or IEnumerable of Anonymous Type from an ApiController method. 


So we’ll mod the method signature too, so that public dynamic Get() becomes


public dynamic Get()
{
 var urls = db.Urls.Include("Tags").Select(i => 
    new { i.Uid, i.UrlId, i.Address, i.Description, i.Tags});
 return urls;
}



That's about it. Now we get our EF objects serialized to JSON with JSON.NET
Finally, here is something for religious MVC followers.

If you don't like to give up on the MVC mandate of programming, then instead of using selective values or returning anonymous objects to the view, you could instead create a custom type, and effectively have a ViewModel intact.

Cheers!


No comments:

Post a Comment