Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EntityFramework to Json workaround? ( A circular reference was detected while serializing an object of type...DynamicProxies) [duplicate]

so heres the deal i have

Models

public class News
{

    public News()
    {
        this.Created = DateTime.Now;
    }

    public int Id { get; set; }       
    public string Title { get; set; }
    public string Preamble { get; set; }
    public string Body { get; set; }
    public DateTime Created { get; set; }

    public int UserId { get; set; }

    public virtual User User { get; set; }

    public int CategoryId { get; set; }
    public int ImageId { get; set; }

    public virtual Image Image { get; set; }
    public virtual Category Category { get; set; }
}

public class Image
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string ImageUrl { get; set; }
    public Byte[] ImageData { get; set; }
    public string ImageMimeType { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

....following models(these models are connected to the EfDbContext) connected to following repository...

Interface/Repository

public class NewsRepository : INewsRepository
{
    EfDbContext context = new EfDbContext();

    public IQueryable<News> All
    {
        get { return context.News; }
    }

    public IQueryable<News> AllIncluding(params Expression<Func<News, object>>[] includeProperties)
    {
        IQueryable<News> query = context.News;
        foreach (var includeProperty in includeProperties) {
            query = query.Include(includeProperty);
        }
        return query;
    }

    public News Find(int id)
    {
        return context.News.Find(id);
    }

    public void InsertOrUpdate(News news)
    {
        if (news.Id == default(int)) {
            // New entity
            context.News.Add(news);
        } else {
            // Existing entity
            context.Entry(news).State = EntityState.Modified;
        }
    }

    public void Delete(int id)
    {
        var news = context.News.Find(id);
        context.News.Remove(news);
    }

    public void Save()
    {
        context.SaveChanges();
    }
}

public interface INewsRepository
{
    IQueryable<News> All { get; }
    IQueryable<News> AllIncluding(params Expression<Func<News, object>>[] includeProperties);
    News Find(int id);
    void InsertOrUpdate(News news);
    void Delete(int id);
    void Save();
}

In my HomeController() i got a a JsonResult metod that i want to return the context. Here is the Method

Json Request

    [HttpGet]
    public JsonResult GetNews()
    {
        var p = newsRepository.AllIncluding(news => news.Category, news => news.Image);
        return Json(p, JsonRequestBehavior.AllowGet);
    }

I get the following error:

A circular reference was detected while serializing an object of type 'System.Data.Entity.DynamicProxies.News_96C0B16EC4AC46070505EEC7537EF3C68EE6CE5FC3C7D8EBB793B2CF9BD391B3'.

I guessed that this has something to do with the lazyloading stuff(Iam currently learning about C#) i found this article about this...

http://hellowebapps.com/2010-09-26/producing-json-from-entity-framework-4-0-generated-classes/

but i didnt get it to work... what i could read about the code was that they were tryin to depth search trough the object... more than that i couldn't figure out.

my question is how to i can pass in lazyLoading objects? into json/serializer or does it not exist, any thoughts of how i can proceed?

like image 293
Martea Avatar asked Sep 30 '11 09:09

Martea


2 Answers

Since Json is a tree-based serialization format, it has problems with references like A->B->A.
I've read somewhere that you can use ScriptIgnore attribute in your viewmodels to prevent this error. But have not tested it.

You can change your code to the following (use anonymous types) to retrieve the items successfully:

 var p = newsRepository.AllIncluding(news => news.Category, news => news.Image)
    .Select(n => new {id = n.Id, Body = n.Body});

Include any other property you wish to display in the last Select method. This makes your Json results more lightweight too.

like image 178
Kamyar Avatar answered Apr 01 '23 19:04

Kamyar


To add to Kamyar's answer...

The AllIncluding method is only available if you are using MVC scaffolding. see the following link for a listing of the method: Mvc 3 Scaffolding: the Model passed to the View throws SQL errror

I tried using it, but still encountered the circular reference error, since the root objects were still being returned as proxies. So I customised the method to temporarily turn off the ProxyCreationEnabled flag on the EF context, and eagerly load the specified properties listed in the method's parameter. See the following link for further details: Loading from database without proxy classes?

In order for this to work, the query had to be performed while the setting was still off, so I had to call the query's ToList() method to perform the query, and then returned the IEnumerable, rather than IQueryable. This did the job for me.

Here is the method I used ("_context" is the variable name for my EF context):

public IEnumerable<TEntity> ListIncluding<TEntity>(params Expression<Func<TEntity, object>>[] includeProperties) 
    where TEntity : class
{
    bool cachedSetting = _context.Configuration.ProxyCreationEnabled;
    _context.Configuration.ProxyCreationEnabled = false;

    IQueryable<TEntity> query = _context.Set<TEntity>();
    foreach (var includeProperty in includeProperties)
    {
        query = query.Include(includeProperty);
    }
    IEnumerable<TEntity> list = query.ToList();
    _context.Configuration.ProxyCreationEnabled = cachedSetting;

    return list;
} 

This can then get called using the following syntax:

IEnumerable<News> newsItems = newsRepository.ListIncluding<News>(news => news.Category, news => news.Image); 
like image 30
Andy Thomas Avatar answered Apr 01 '23 19:04

Andy Thomas