web-dev-qa-db-fra.com

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

J'essaie de faire une simple déclaration JSON, mais j'ai des problèmes. J'ai les éléments suivants ci-dessous.

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

Je reçois un HTTP 500 à l'exception de ce qui est indiqué dans le titre de cette question. J'ai aussi essayé

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

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

Est-ce un bug ou mon implémentation?

166
Jon

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 avez plusieurs propriétés, AutoMapper peut être utilisé pour automatiquement mapper entre les objets DTO et les objets View.

172
Darin Dimitrov

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");
95
ddfnfal

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

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

Juste en expliquant la réponse finale, le code entier 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 pourrait également être le cas si vous ne voulez pas que les objets se trouvent dans une propriété Result:

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
           );
}
54
ClayKaboom

Pour résumer, il y a 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 qu'il ignore 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 identiques, mais utiliser un modèle est préférable car il est typé fort.

Solution 3: renvoyez un modèle contenant 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: renvoyer un nouvel objet dynamique contenant 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);
        }
    }
13
Amro

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 sur un certain chemin. Par exemple, avec XmlSerializer, vous 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 DatabaseColumn dispose de marqueurs appropriés (très peu probable, car il faudrait référencer chaque API de sérialisation).

7
Marc Gravell

ajoutez [JsonIgnore] aux propriétés de la machine virtuelle de votre modèle.

Cela est dû au nouveau modèle DbContext T4 utilisé pour générer les entités EntityFramework. Pour pouvoir effectuer le suivi des modifications, ces modèles utilisent le modèle Proxy, en encapsulant vos POCO de Nice. Cela provoque alors les problèmes lors de la sérialisation avec JavaScriptSerializer.

Alors les 2 solutions sont:

  1. Soit vous venez de 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 de mandataires en la définissant dans 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/

4
nilesh

Évitez de convertir l'objet table directement. Si des relations sont définies entre d'autres tables, cette erreur peut être générée. Vous pouvez plutôt créer une classe de modèle, affecter des valeurs à l'objet de classe, puis le sérialiser.

4
Unais.N.I

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;
4
kravits88

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

Enquête

La fonction MVC's Controller.Json fait le travail, mais elle est très mauvaise pour fournir une erreur pertinente dans ce cas. En utilisant Newtonsoft.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.

Architecture appropriée

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 de l'ORM constituent le chemin de la perdition en matière de sérialisation. Le flux de données devrait ê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 le manque 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 requiert (c'est-à-dire les propriétés).

Dans les rares cas où 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 identificateurs, pas des références).

Les applications modernes ont tendance à éviter de charger simultanément des structures de données complexes 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 des données d'en-tête
  2. liste des participants gérés - accéder à une fenêtre contextuelle et charger paresseux la liste -> modèle de service (JSON) contenant uniquement la liste des participants
2
Alexei

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

En action

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

une 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_;  
    }
1
A.Kosecik

Vous pouvez remarquer les propriétés à l'origine de 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
}
0
Bassil