I have my Entity Framework Entities split out into a separate class library from my web project and data access layer. In my controller I make a call to my repository to get an IEnumerable<RobotDog.Entities.Movie>
and then try to serialize into json using JavaScriptSerializer
but I get a circular reference even though I'm using the [ScriptIgnore]
attribute.
IMPORTANT: Originally I had my entities, data access and web all under one project and I was able to successfully serialize my entites without a circular reference. When I created separate layers that's when I started having problems. I did not change any of the entities.
An example of one of my entities in the RobotDog.Entities
namespace:
namespace RobotDog.Entities {
public class Character {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[MaxLength(200)]
public string Name { get; set; }
public virtual Person Person { get; set; }
[ScriptIgnore]
public virtual Movie Movie { get; set; }
}
}
My controller:
namespace RobotDog.Web.Controllers {
public class MoviesController : Controller {
private UnitOfWork _unitOfWork = new UnitOfWork();
[HttpGet]
public ActionResult Index() {
var user = Membership.GetUser(User.Identity.Name);
if(user != null) {
var movies = _unitOfWork.UserMovieRepository.Get(u => u.UserId == (Guid) user.ProviderUserKey).Select(m => m.Movie);
var serializer = new JavaScriptSerializer();
var json = serializer.Serialize(movies);
return View(json);
}
return View();
}
}
}
My Repository:
namespace RobotDog.DataAccess.Movies {
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class {
internal MovieContext Context;
internal DbSet<TEntity> DbSet;
public Repository(MovieContext context) {
if (context == null)
throw new ArgumentNullException("context");
Context = context;
DbSet = Context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null ) {
IQueryable<TEntity> query = DbSet;
if (predicate != null)
query = query.Where(predicate);
return orderBy != null ? orderBy(query).ToList() : query.ToList();
}
}
}
The JavaScriptSerializer class is used internally by the asynchronous communication layer to serialize and deserialize the data that is passed between the browser and the Web server.
Circular reference occurs when two or more interdependent resources cause lock condition. This makes the resource unusable. To handle the problem of circular references in C#, you should use garbage collection. It detects and collects circular references.
Maybe kinda late response, but I had similar problem with POCO Classes for Entity Framework Code-Firts. The problem was that may properties were declared as virtual. In this case EF creates proxy class which overrides the virtual property. It seems that ScriptIgnore attribute is not by default applied on overriden properties, unless you use it like this:
[ScriptIgnore(ApplyToOverrides=true)]
Circular object graphs cannot be JSON serialized. And when you give it a second thought it actually makes sense. The correct way to handle this is to use view models. You should never pass your domain entities directly to your views. Always define a view model containing only the necessary properties that you want to be exposed.
I am sure that the client consuming this JSON doesn't care about having this circular object graph. So simply define a view model breaking this circular dependency and including only the properties you need.
Then all you have to do is map your domain model to the view model and pass this view model to a JsonResult (yeah that's another issue in your code - you are manually JSON serializing and writing plumbing code in your controller action instead of delegating this to the framework).
So:
[HttpGet]
public ActionResult Index()
{
var user = Membership.GetUser(User.Identity.Name);
if(user != null)
{
IEnumerable<Movie> movies = _unitOfWork
.UserMovieRepository.Get(u => u.UserId == (Guid) user.ProviderUserKey)
.Select(m => m.Movie);
IEnumerable<MovieViewModel> moviesVm = ... map the domain model to your view model
return Json(moviesVm, JsonRequestBehavior.AllowGet);
}
// return an empty movies array
var empty = Enumerable.Empty<MovieViewModel>();
return Json(empty, JsonRequestBehavior.AllowGet);
}
The important thing you should be focusing right now on is defining the MovieViewModel
class which will contain only the information that you want to expose to the client as JSON. Break all circular references. Feel free to have additional view models that this main view model is referencing in order to map other entities.
And most importantly : never pass your domain models to the view. Always define view models. This way your application is completely independent of the underlying data access technology you are using. You could modify your DAL layer as much as you like without impacting the UI part because this UI is represented by view models.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With