117 votes

Entity framework boucle de référencement autodétectée

J'ai une erreur étrange. Je suis en train d'expérimenter avec un Web API .NET 4.5, Entity Framework et MS SQL Server. J'ai déjà créé la base de données et configuré les clés primaires et étrangères correctes ainsi que les relations.

J'ai créé un modèle .edmx et importé deux tables : Employee et Department. Un département peut avoir plusieurs employés et cette relation existe. J'ai créé un nouveau contrôleur appelé EmployeeController en utilisant les options de génération de code pour créer un contrôleur API avec des actions de lecture/écriture en utilisant Entity Framework. Dans l'assistant, j'ai sélectionné Employee comme modèle et l'entité correcte pour le contexte de données.

La méthode qui est créée ressemble à ceci :

public IEnumerable GetEmployees()
{
    var employees = db.Employees.Include(e => e.Department);
    return employees.AsEnumerable();
}

Quand j'appelle mon API via /api/Employee, j'obtiens cette erreur :

Le type 'ObjectContent`1' n'a pas réussi à sérialiser le corps de réponse pour le type de contenu 'application/json; ...System.InvalidOperationException","StackTrace":null,"InnerException":{"Message":"Une erreur s'est produite.","ExceptionMessage":"Détection de boucle auto-référente avec le type 'System.Data.Entity.DynamicProxies.Employee_5D80AD978BC68A1D8BD675852F94E8B550F4CB150ADB8649E8998B7F95422552'. Chemin '[0].Department.Employees'.","ExceptionType":"Newtonsoft.Json.JsonSerializationException","StackTrace":" ...

Pourquoi y a-t-il une auto-référence [0].Department.Employees? Cela n'a pas beaucoup de sens. Je m'attendrais à ce que cela se produise si j'avais des références circulaires dans ma base de données mais c'est un exemple très simple. Qu'est-ce qui pourrait mal se passer?

191voto

Pedro Figueiredo Points 2166

Eh bien, la réponse correcte pour le formateur Json par défaut basé sur Json.net est de définir ReferenceLoopHandling sur Ignore.

Il suffit d'ajouter ceci à Application_Start dans Global.asax :

HttpConfiguration config = GlobalConfiguration.Configuration;

config.Formatters.JsonFormatter
            .SerializerSettings
            .ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

C'est la bonne façon de faire. Cela ignorera la référence pointant vers l'objet.

D'autres réponses se sont concentrées sur la modification de la liste renvoyée en excluant des données ou en créant un objet façade, et parfois cela n'est pas une option.

Utiliser l'attribut JsonIgnore pour restreindre les références peut être chronophage et si vous voulez sérialiser l'arborescence à partir d'un autre point, cela posera problème.

56voto

Cela se produit parce que vous essayez de sérialiser directement la collection d'objets EF. Puisque le département est associé à l'employé et l'employé au département, le sérialiseur JSON bouclera indéfiniment en lisant d.Employee.Departments.Employee.Departments, etc...

Pour résoudre ce problème juste avant la sérialisation, créez un type anonyme avec les propriétés que vous souhaitez

Exemple de code (pseudo) :

departments.select(dep => new { 
    dep.Id, 
    Employee = new { 
        dep.Employee.Id, dep.Employee.Name 
    }
});

36voto

B-Lat Points 1625

J'avais le même problème et j'ai constaté que vous pouvez simplement appliquer l'attribut [JsonIgnore] à la propriété de navigation que vous ne voulez pas sérialiser. Cela sérialisera toujours à la fois les entités parent et enfant mais évite simplement la boucle de référence automatique.

22voto

Ludwik11 Points 832

Je suis conscient que la question est assez ancienne, mais elle est encore populaire et je ne vois aucune solution pour ASP.net Core.

Dans le cas d'ASP.net Core, vous devez ajouter un nouveau JsonOutputFormatter dans le fichier Startup.cs:

    public void ConfigureServices(IServiceCollection services)
    {

        services.AddMvc(options =>
        {
            options.OutputFormatters.Clear();
            options.OutputFormatters.Add(new JsonOutputFormatter(new JsonSerializerSettings()
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            }, ArrayPool.Shared));
        });

        //...
    }

Après l'avoir mis en œuvre, le sérialiseur JSON ignorera simplement les références en boucle. Cela signifie qu'il renverra null au lieu de charger indéfiniment des objets se référençant mutuellement.

Sans la solution ci-dessus en utilisant :

var employees = db.Employees.ToList();

Chargerait les Employees et les Departments qui leur sont liés.

Après avoir défini ReferenceLoopHandling sur Ignore, les Departments seront définis sur null à moins que vous ne les incluiez dans votre requête :

var employees = db.Employees.Include(e => e.Department);

De plus, gardez à l'esprit que cela effacera tous les OutputFormatters, si vous ne le souhaitez pas, vous pouvez essayer de supprimer cette ligne :

options.OutputFormatters.Clear();

Mais la supprimer provoque à nouveau une exception de boucle d'auto-référence dans mon cas pour une raison quelconque.

9voto

AndroidUser Points 434

Le principal problème est que la sérialisation d'un modèle d'entité qui a une relation avec un autre modèle d'entité (relation de clé étrangère). Cette relation provoque une auto-référence qui entraînera une exception lors de la sérialisation en json ou xml. Il existe de nombreuses options. Sans sérialiser les modèles d'entité en utilisant des modèles personnalisés. Les valeurs ou les données du modèle d'entité sont mappées vers des modèles personnalisés (mapping d'objets) en utilisant Automapper or Valueinjector puis renvoyées et sérialisées sans aucun autre problème. Ou vous pouvez sérialiser le modèle d'entité en premier lieu en désactivant les proxies dans le modèle d'entité

public class LabEntities : DbContext
{
   public LabEntities()
   {
      Configuration.ProxyCreationEnabled = false;
   }

Pour préserver les références d'objet en XML, vous avez deux options. L'option la plus simple est d'ajouter [DataContract(IsReference=true)] à votre classe de modèle. Le paramètre IsReference permet les références d'objets. Rappelez-vous que DataContract rend la sérialisation opt-in, vous devrez donc également ajouter des attributs DataMember aux propriétés :

[DataContract(IsReference=true)]
public partial class Employee
{
   [DataMember]
   string dfsd{get;set;}
   [DataMember]
   string dfsd{get;set;}
   //exclure la relation sans donner l'étiquette datamember
   List Departments{get;set;}
}

En format JSON dans global.asax

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.All;

En format XML

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Employee), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer(dcs);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X