web-dev-qa-db-fra.com

Boucle d'auto-référencement de la structure d'entité détectée

J'ai une erreur étrange. J'expérimente avec une API Web .NET 4.5, Entity Framework et MS SQL Server. J'ai déjà créé la base de données et mis en place les clés et relations primaires et étrangères appropriées.

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 à l'aide des options d'échafaudage pour créer un contrôleur d'API avec des actions de lecture/écriture à l'aide d'Entity Framework. Dans l'assistant, sélectionnez Employee comme modèle et l'entité appropriée pour le contexte de données.

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

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

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

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

Pourquoi est-il auto-référençant [0] .Department.Employees? Cela n'a pas beaucoup de sens. Je m'attendrais à ce que cela se produise si la base de données était référencée de manière circulaire, mais il s'agit d'un exemple très simple. Qu'est-ce qui pourrait mal tourner?

81
Melon

La réponse correcte pour le formateur Json par défaut basé sur Json.net consiste à définir ReferenceLoopHandling sur Ignore.

Ajoutez simplement ceci au Application_Start dans Global.asax:

HttpConfiguration config = GlobalConfiguration.Configuration;

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

C'est la bonne façon. Il ignorera la référence renvoyant à l'objet.

D'autres réponses visaient principalement à modifier la liste renvoyée en excluant des données ou en créant un objet de façade, ce qui n'est parfois pas une option.

L'utilisation de l'attribut JsonIgnore pour limiter les références peut prendre beaucoup de temps. Si vous souhaitez sérialiser l'arborescence à partir d'un autre point, cela posera un problème.

136
Pedro Figueiredo

Cela est dû au fait que vous essayez de sérialiser directement la collection d'objets EF. Étant donné que chaque service est associé à un employé et que chaque employé à un autre service, le sérialiseur JSON lira infiniment en lisant d.

Pour corriger cela avant la sérialisation, créez un type anonyme avec les accessoires souhaités.

exemple de code (pseudo):

departments.select(dep => new { 
    dep.Id, 
    Employee = new { 
        dep.Employee.Id, dep.Employee.Name 
    }
});
48
Nicolás Straub

J'ai eu le même problème et j'ai constaté que vous pouvez simplement appliquer l'attribut [JsonIgnore] à la propriété de navigation pour laquelle vous ne souhaitez pas être sérialisé. Il continuera à sérialiser les entités parent et enfant, mais évite simplement la boucle d'auto-référencement.

29
B-Lat

Je sais que cette question est assez ancienne, mais elle reste populaire et je ne vois aucune solution pour ASP.net Core.

Dans le cas de ASP.net Core, vous devez ajouter une nouvelle variable 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<char>.Shared));
        });

        //...
    }

Une fois implémenté, le sérialiseur JSON ignorera simplement les références de boucle. Cela signifie: il renverra null au lieu de charger indéfiniment des objets se référant les uns aux autres.

Sans solution ci-dessus en utilisant:

var employees = db.Employees.ToList();

Est-ce que charger Employees et les relier Departments.

Après avoir défini ReferenceLoopHandling sur Ignore, Departments sera défini sur null sauf si vous l'incluez dans votre requête:

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

Aussi, gardez à l'esprit que cela effacera tout OutputFormatters , si vous ne voulez pas, vous pouvez essayer de supprimer cette ligne:

options.OutputFormatters.Clear();

Mais le supprimer provoque à nouveau une exception self referencing loop dans mon cas pour une raison quelconque.

12
Piotrek

Le problème principal est celui de la sérialisation d'un modèle d'entité ayant une relation avec un autre modèle d'entité (relation de clé étrangère). Cette relation provoque l'auto-référencement, ce qui jettera une exception lors de la sérialisation en json ou xml . Il y a beaucoup d'options. Sans sérialiser les modèles d'entité à l'aide de modèles personnalisés. Valeurs ou données provenant de données de modèle d'entité mappées sur des modèles personnalisés (mappage d'objet) à l'aide de Automapper ou Valueinjector puis renvoie la demande et il sera sérialisé sans autre problème ..__ Si vous pouvez sérialiser le modèle d'entité, commencez par désactiver les proxiesin le modèle d'entité 

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

Pour conserver les références d'objet en XML, vous avez deux options. L'option la plus simple consiste à ajouter [DataContract (IsReference = true)] à votre classe de modèle. Le paramètre IsReference active les références à un objet. N'oubliez pas que DataContract autorise la sérialisation, vous devez donc 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;}
   //exclude  the relation without giving datamember tag
   List<Department> Departments{get;set;}
}

Au format Jsonin global.asax

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

au format xml

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

Le message d'erreur signifie que vous avez une boucle d'auto-référencement.

Le JSON que vous produisez ressemble à cet exemple (avec la liste d’un employé):

[
employee1 : {
    name: "name",
    department : {
        name: "departmentName",
        employees : [
            employee1 : {
                name: "name",
                department : {
                    name: "departmentName",
                    employees : [
                        employee1 : {
                            name: "name",
                            department : {
                                and again and again....
                            }
                    ]
                }
            }
        ]
    }
}

]

Vous devez indiquer au contexte de la base de données que vous ne souhaitez pas obtenir toutes les entités liées lorsque vous demandez quelque chose .. L'option pour DbContext est Configuration.LazyLoadingEnabled

Le meilleur moyen que j'ai trouvé est de créer un contexte pour la sérialisation:

public class SerializerContext : LabEntities 
{
    public SerializerContext()
    {
        this.Configuration.LazyLoadingEnabled = false;
    }
}
7
Thomas B. Lze

Ajoutez une ligne Configuration.ProxyCreationEnabled = false; dans le constructeur de la définition de classe partielle de votre modèle de contexte.

    public partial class YourDbContextModelName : DbContext
{
    public YourDbContextModelName()
        : base("name=YourDbContextConn_StringName")
    {
        Configuration.ProxyCreationEnabled = false;//this is line to be added
    }

    public virtual DbSet<Employee> Employees{ get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    }
}
6
newstockie

Je n'avais qu'un seul modèle à utiliser, je me suis donc retrouvé avec le code suivant:

var JsonImageModel = Newtonsoft.Json.JsonConvert.SerializeObject(Images, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
3
sindrem

Je pourrais aussi chercher à ajouter des exemples explicites pour chaque contrôleur/action, comme indiqué ici:

http://blogs.msdn.com/b/yaohuang1/archive/2012/10/13/asp-net-web-api-help-page-part-2-providing-custom-samples-on-the- help-page.aspx

c'est-à-dire config.SetActualResponseType (typeof (SomeType), "Values", "Get");

0
sobelito

Si vous essayez de modifier ce paramètre dans le modèle Blazor (ASP.NET Core Hosted), vous devez transmettre ce qui suit à l'appel AddNewtonsoftJson dans Startup.cs dans le projet Server:

services.AddMvc().AddNewtonsoftJson(options => 
    options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore
);

Je viens d'avoir le même problème sur mon site principal .net. La réponse acceptée ne fonctionnait pas pour moi, mais j'ai constaté qu'une combinaison de ReferenceLoopHandling.Ignore et PreserveReferencesHandling.Objects l'a corrigée.

//serialize item
var serializedItem = JsonConvert.SerializeObject(data, Formatting.Indented, 
new JsonSerializerSettings
{
     PreserveReferencesHandling = PreserveReferencesHandling.Objects,
     ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
0
D. Dahlberg

auto-référence comme exemple

=============================================== =========== 

public class Employee
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public int ManagerId { get; set; }
    public virtual Employee Manager { get; set; }

    public virtual ICollection<Employee> Employees { get; set; }

    public Employee()
    {
        Employees = new HashSet<Employee>();
    }
}

=============================================== =========== 

        HasMany(e => e.Employees)
            .WithRequired(e => e.Manager)
            .HasForeignKey(e => e.ManagerId)
            .WillCascadeOnDelete(false);
0
counter arguments