Une référence circulaire a été détectée lors de la sérialisation d'un objet de type 'SubSonic.Schema .DatabaseColumn'.

170

J'essaie de faire un simple retour JSON mais j'ai des problèmes ci-dessous.

public JsonResult GetEventData()
{
    var data = Event.Find(x => x.ID != 0);
    return Json(data);
}

J'obtiens un HTTP 500 à l'exception comme indiqué dans le titre de cette question. J'ai aussi essayé

var data = Event.All().ToList()

Cela a posé le même problème.

Est-ce un bug ou mon implémentation?

Jon
la source
1
Regarder celui-ci. Il existe une solution utilisant l' ScriptIgnoreattribut. stackoverflow.com/questions/1193857/subsonic-3-0-0-2-structs-tt
freddoo
C'était la meilleure solution pour moi; J'ai eu Jeu> Tournoi> Jeu> Tournoi> Jeu, etc. J'ai placé un ScriptIgnoreattribut sur la propriété Tournament.Game et cela a bien fonctionné :)
eth0
Au cas où quelqu'un souhaiterait une solution «automatisée» (et non la meilleure pratique) pour ce problème qui ne nécessite aucun code supplémentaire, consultez ce QA: Ne sérialisez pas les références de classe Entity Framework dans JSON (bibliothèque ServiceStack.Text)
nikib3ro

Réponses:

175

Il semble qu'il existe des références circulaires dans votre hiérarchie d'objets qui ne sont pas prises en charge par le sérialiseur JSON. Avez-vous besoin de toutes les colonnes? Vous pouvez sélectionner uniquement les propriétés dont vous avez besoin dans la vue:

return Json(new 
{  
    PropertyINeed1 = data.PropertyINeed1,
    PropertyINeed2 = data.PropertyINeed2
});

Cela rendra votre objet JSON plus léger et plus facile à comprendre. Si vous disposez de nombreuses propriétés, AutoMapper peut être utilisé pour mapper automatiquement entre les objets DTO et les objets View.

Darin Dimitrov
la source
Je pense que la sélection de ceux que je veux peut fonctionner.Je pense que la référence circulaire est due au fait que dans Event, j'ai IQueryable <Category> qui à son tour aura un IQueryable <Event>
Jon
7
Automapper ne garantit pas que vous n'obtiendrez pas ce problème. Je suis venu ici à la recherche d'une réponse et j'utilise actuellement automapper.
Captain Kenpachi
1
Voyez la réponse de @ClayKaboom car elle explique pourquoi elle pourrait être circulaire
PandaWood
106

J'ai eu le même problème et résolu par using Newtonsoft.Json;

var list = JsonConvert.SerializeObject(model,
    Formatting.None,
    new JsonSerializerSettings() {
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
});

return Content(list, "application/json");
ddfnfal
la source
3
Ce code en ligne a bien fonctionné pour moi. Le même truc dans la configuration globale comme mentionné par kravits88 ne fonctionne pas pour moi. ÉGALEMENT, la signature de méthode doit être mise à jour pour renvoyer ContentResult pour ce code.
BiLaL
6
Cela devrait être marqué comme la meilleure réponse, car elle couvre les cas où vous ne pouvez pas passer des heures à convertir vos objets en d'autres représentations comme dans la réponse marquée comme acceptée.
Renan
56

Cela se produit en fait parce que les objets complexes sont ce qui provoque l'échec de l'objet json résultant. Et cela échoue parce que lorsque l'objet est mappé, il mappe les enfants, qui mappe leurs parents, faisant apparaître une référence circulaire. Json prendrait un temps infini pour le sérialiser, donc il prévient le problème avec l'exception.

Le mappage Entity Framework produit également le même comportement et la solution consiste à ignorer toutes les propriétés indésirables.

En expliquant simplement la réponse finale, tout le code serait:

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           new {
                Result = (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
               }
           , JsonRequestBehavior.AllowGet
           );
}

Cela peut également être le suivant si vous ne souhaitez pas que les objets se trouvent dans une Resultpropriété:

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
           , JsonRequestBehavior.AllowGet
           );
}
ArgileKaboom
la source
1
+1 pour des choses claires et faciles à comprendre, merci @Clay. J'aime votre explication sur les concepts derrière l'erreur.
Ajay2707
14

Pour résumer, il existe 4 solutions à cela:

Solution 1: désactivez ProxyCreation pour le DBContext et restaurez-le à la fin.

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        bool proxyCreation = db.Configuration.ProxyCreationEnabled;
        try
        {
            //set ProxyCreation to false
            db.Configuration.ProxyCreationEnabled = false;

            var data = db.Products.ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
        finally
        {
            //restore ProxyCreation to its original state
            db.Configuration.ProxyCreationEnabled = proxyCreation;
        }
    }

Solution 2: utilisation de JsonConvert en définissant ReferenceLoopHandling pour ignorer les paramètres du sérialiseur.

    //using using Newtonsoft.Json;

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.ToList();

            JsonSerializerSettings jss = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
            var result = JsonConvert.SerializeObject(data, Formatting.Indented, jss);

            return Json(result, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

Les deux solutions suivantes sont les mêmes, mais l'utilisation d'un modèle est préférable car il est fortement typé.

Solution 3: renvoyez un modèle qui inclut uniquement les propriétés nécessaires.

    private DBEntities db = new DBEntities();//dbcontext

    public class ProductModel
    {
        public int Product_ID { get; set;}

        public string Product_Name { get; set;}

        public double Product_Price { get; set;}
    }

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.Select(p => new ProductModel
                                                {
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                }).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

Solution 4: renvoyez un nouvel objet dynamique qui inclut uniquement les propriétés nécessaires.

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.Select(p => new
                                                {
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                }).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }
Amro
la source
7

JSON, comme xml et divers autres formats, est un format de sérialisation basé sur une arborescence. Il ne vous aimera pas si vous avez des références circulaires dans vos objets, comme le serait «l'arbre»:

root B => child A => parent B => child A => parent B => ...

Il existe souvent des moyens de désactiver la navigation le long d'un certain chemin; par exemple, avec XmlSerializervous pouvez marquer la propriété parent comme XmlIgnore. Je ne sais pas si cela est possible avec le sérialiseur json en question, ni si DatabaseColumna des marqueurs appropriés ( très peu probable, car il faudrait référencer chaque API de sérialisation)

Marc Gravell
la source
4

C'est à cause du nouveau modèle DbContext T4 qui est utilisé pour générer les entités EntityFramework. Afin de pouvoir effectuer le suivi des modifications, ce modèle utilise le modèle Proxy, en enveloppant vos jolis POCO avec eux. Cela provoque alors les problèmes lors de la sérialisation avec JavaScriptSerializer.

Alors les 2 solutions sont:

  1. Soit vous ne faites que sérialiser et renvoyer les propriétés dont vous avez besoin sur le client
  2. Vous pouvez désactiver la génération automatique des proxies en la paramétrant sur la configuration du contexte

    context.Configuration.ProxyCreationEnabled = false;

Très bien expliqué dans l'article ci-dessous.

http://juristr.com/blog/2011/08/javascriptserializer-circular-reference/

nilesh
la source
4

Utilisation de Newtonsoft.Json: Dans votre méthode Global.asax Application_Start, ajoutez cette ligne:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
kravits88
la source
1
Apparemment, ça a l'air très simple mais cela n'a pas fonctionné pour moi
BiLaL
4

ajouter [JsonIgnore]aux propriétés virtuelles de votre modèle.

MorenajeRD
la source
4

Évitez de convertir directement l'objet table. Si des relations sont définies entre d'autres tables, cela peut générer cette erreur. Au contraire, vous pouvez créer une classe de modèle, attribuer des valeurs à l'objet de classe, puis le sérialiser.

Unais.NI
la source
3

Les réponses fournies sont bonnes, mais je pense qu'elles peuvent être améliorées en ajoutant une perspective «architecturale».

Enquête

MVC's Controller.Jsonla fonction fait le travail, mais elle est très faible pour fournir une erreur pertinente dans ce cas. En utilisantNewtonsoft.Json.JsonConvert.SerializeObject , l'erreur spécifie exactement quelle est la propriété qui déclenche la référence circulaire. Ceci est particulièrement utile lors de la sérialisation de hiérarchies d'objets plus complexes.

Une bonne architecture

Il ne faut jamais essayer de sérialiser des modèles de données (par exemple des modèles EF), car les propriétés de navigation d'ORM sont la voie vers la perdition en matière de sérialisation. Le flux de données doit être le suivant:

Database -> data models -> service models -> JSON string 

Les modèles de service peuvent être obtenus à partir de modèles de données à l'aide de mappeurs automatiques (par exemple Automapper ). Bien que cela ne garantisse pas l'absence de références circulaires, une conception appropriée devrait le faire: les modèles de service devraient contenir exactement ce que le consommateur de service a besoin (c'est-à-dire les propriétés).

Dans ces rares cas, lorsque le client demande une hiérarchie impliquant le même type d'objet à différents niveaux, le service peut créer une structure linéaire avec une relation parent-> enfant (en utilisant uniquement des identifiants, pas des références).

Les applications modernes ont tendance à éviter de charger des structures de données complexes à la fois et les modèles de service doivent être minces. Par exemple:

  1. accéder à un événement - seules les données d'en-tête (identifiant, nom, date, etc.) sont chargées -> modèle de service (JSON) contenant uniquement les données d'en-tête
  2. liste des participants gérée - accédez à une fenêtre contextuelle et chargez la liste paresseusement -> modèle de service (JSON) contenant uniquement la liste des participants
Alexei
la source
1

J'utilise le correctif, car j'utilise Knockout dans les vues MVC5.

En action

return Json(ModelHelper.GetJsonModel<Core_User>(viewModel));

fonction

   public static TEntity GetJsonModel<TEntity>(TEntity Entity) where TEntity : class
    {
        TEntity Entity_ = Activator.CreateInstance(typeof(TEntity)) as TEntity;
        foreach (var item in Entity.GetType().GetProperties())
        {
            if (item.PropertyType.ToString().IndexOf("Generic.ICollection") == -1 && item.PropertyType.ToString().IndexOf("SaymenCore.DAL.") == -1)
                item.SetValue(Entity_, Entity.GetPropValue(item.Name));
        }
        return Entity_;  
    }
A.Kosecik
la source
0

Vous pouvez remarquer les propriétés qui provoquent la référence circulaire. Ensuite, vous pouvez faire quelque chose comme:

private Object DeCircular(Object object)
{
   // Set properties that cause the circular reference to null

   return object
}
Bassel
la source
-1
//first: Create a class as your view model

public class EventViewModel 
{
 public int Id{get;set}
 public string Property1{get;set;}
 public string Property2{get;set;}
}
//then from your method
[HttpGet]
public async Task<ActionResult> GetEvent()
{
 var events = await db.Event.Find(x => x.ID != 0);
 List<EventViewModel> model = events.Select(event => new EventViewModel(){
 Id = event.Id,
 Property1 = event.Property1,
 Property1 = event.Property2
}).ToList();
 return Json(new{ data = model }, JsonRequestBehavior.AllowGet);
}
Ynnoboy
la source
Cela ne répond pas à la question
Dane I
-1

Une alternative plus simple pour résoudre ce problème consiste à renvoyer une chaîne et à formater cette chaîne en json avec JavaScriptSerializer.

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
   return j.Serialize(entityList );
}

Il est important de la partie "Sélectionner", qui choisit les propriétés que vous souhaitez dans votre vue. Certains objets ont une référence pour le parent. Si vous ne choisissez pas les attributs, la référence circulaire peut apparaître, si vous prenez simplement les tableaux dans leur ensemble.

Ne faites pas cela:

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.toList();
   return j.Serialize(entityList );
}

Faites ceci à la place si vous ne voulez pas la table entière:

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
   return j.Serialize(entityList );
}

Cela permet de rendre une vue avec moins de données, juste avec les attributs dont vous avez besoin, et accélère votre Web.

Sterling Diaz
la source