web-dev-qa-db-fra.com

Une référence circulaire a détecté une exception lors de la sérialisation d'un objet en JSON

Comme mentionné dans this post, je reçois une erreur de sérialisation Json lors de la sérialisation d’un proxy Entity Framework:

Une référence circulaire a été détectée lors de la sérialisation d'un objet de type 'System.Data.Entity.DynamicProxies.PurchaseOrder_446B939192F161CDBC740067F174F7A6059B0F9C0EEE68CD3EBBD63CF9AF5BD0'.

Mais la différence est que je n'ai pas de référence circulaire dans mes entités et que seulement se produit dans notre environnement de production. Localement, tout fonctionne bien ...

Mes entités:

public interface IEntity
{
    Guid UniqueId { get; }
    int Id { get; }
} 

public class Entity : IEntity
{
    public int Id { get; set; }
    public Guid UniqueId { get; set; }
}

public class PurchaseOrder : Entity
{
    public string Username { get; set; }
    public string Company { get; set; }

    public string SupplierId { get; set; }
    public string SupplierName { get; set; }

    public virtual ICollection<PurchaseOrderLine> Lines { get; set; }
}

public class PurchaseOrderLine : Entity
{
    public string Code { get; set; }
    public string Name { get; set; }
    public decimal Quantity { get; set; }
}

L'action GetCurrent sur mon PurchaseOrderController levant l'exception:

public class PurchaseOrderController : Controller
{
    private readonly IUnitOfWork _unitOfWork;

    public PurchaseOrderController(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public JsonResult GetCurrent()
    {
        return Json(EnsurePurchaseOrder(), JsonRequestBehavior.AllowGet);
    }

    private PurchaseOrder EnsurePurchaseOrder()
    {
        var company = RouteData.GetRequiredString("company");
        var repository = _unitOfWork.GetRepository<PurchaseOrder>();

        var purchaseOrder = repository
                .Include(p => p.Lines)
                .FirstOrDefault
                (
                    p => p.Company == company && 
                         p.Username == User.Identity.Name
                );

        if (purchaseOrder == null)
        {
            purchaseOrder = repository.Create();
            purchaseOrder.UniqueId = Guid.NewGuid();
            purchaseOrder.Company = company;
            purchaseOrder.Username = User.Identity.Name;
            _unitOfWork.SaveChanges();
        }

        return purchaseOrder;
    }
}
34
Mathijs

Vos entités POCO sont parfaitement sérialisables. Votre problème est que les mandataires dynamiques que le runtime EF crée pour vous ne le sont généralement pas. Vous pouvez définir le context.Configuration.ProxyCreationEnabled sur false, mais vous perdez le chargement paresseux. Je vous recommande fortement d'utiliser Json.NET qui prend en charge la sérialisation pour les entités EF:

Le support ADO.NET Entity Framework ajouté par erreur à Json.NET

Framework JSON hautes performances populaire pour .NET

30

Option 1 (recommandé)

Essayez de désactiver la création d’objet proxy sur votre DbContext .

DbContext.Configuration.ProxyCreationEnabled = false;

Généralement, ce scénario est dû au fait que l'application utilise des objets POCO (générés par T4 ou code-premier). Le problème se pose lorsque Entity Framework souhaite suivre les modifications dans votre objet qui ne sont pas intégrées aux objets POCO. Pour résoudre ce problème, EF crée des objets proxy dépourvus des attributs des objets POCO et ne pouvant pas être sérialisés.

Les raisons pour lesquelles je recommande cette approche; L'utilisation d'un site Web signifie que vous n'avez probablement pas besoin du suivi des modifications (avec état) sur les objets Entity Framework, cela libère de la mémoire et du processeur, car le suivi des modifications est désactivé et il fonctionnera de la même manière sur tous vos objets.

Option 2 

Utilisez un sérialiseur (tel que JSON.Net qui est déjà inclus dans ASP.Net 4) qui permet la personnalisation de la sérialisation du ou des objets.

La raison pour laquelle je ne recommande pas cette approche est que, éventuellement, la logique de sérialisation d'objet personnalisé nécessitera des objets proxy en série sous la forme de types d'objets other. Cela signifie que vous dépendez de la logique pour fournir un résultat en aval. Changer d'objet signifie changer de logique. Dans un projet ASP.Net MVC (toute version), au lieu de changer uniquement une vue, vous devez changer quelque chose qui n'est pas facilement connu en dehors de celui qui a écrit la logique en premier.

Option 3 (Entity Framework 5.x +)

Utilisez .AsNoTracking () qui désactive les objets proxy de la requête spécifique. Si vous devez utiliser le suivi des modifications, cela vous permet d’apporter une solution intermédiaire agréable à la solution n ° 1 de Nice.

42
Erik Philips

J'ai passé d'innombrables heures à essayer toutes les solutions que j'ai trouvées dispersées sur le Web, notamment:

  • [JsonIgnore]
  • Getters internes
  • Désactiver LazyLoadingEnabled et ProxyCreationEnabled
  • Définition de ReferenceLoopHandling sur "ignore"
  • Utiliser avec précaution le chargement explicite si nécessaire

Tout cela s'est finalement avéré infructueux pour moi. Ignorer une propriété a aidé une requête, mais en a blessé trois autres. Cela ressemblait à la programmation équivalente à whack-a-mole. 

Le contexte de mon problème était que les données entrant dans une de mes applications devaient être JSON. Aucun moyen de le contourner. Les insertions et les mises à jour posent évidemment beaucoup moins de problèmes. Mais sélectionner des données stockées dans une base de données normalisée (et dans mon cas incluant un historique de versions) à sérialiser est un cauchemar.

La solution:

Renvoie les données (propriétés) dont vous avez besoin en tant qu'objets anonymes.

Un exemple de code:

Dans ce cas, j'avais besoin des 3 derniers billets, basés sur "Date prévue". Mais aussi besoin de plusieurs propriétés stockées dans des entités liées.

var tickets =
     context.TicketDetails
    .Where(t => t.DateScheduled >= DateTime.Now)
    .OrderBy(t => t.DateScheduled)
    .Take(3)
    .Include(t => t.Ticket)
    .Include(t => t.Ticket.Feature)
    .Include(t => t.Ticket.Feature.Property)
    .AsEnumerable()
    .Select(
        t =>
        new {
            ID = t.Ticket.ID,
            Address = t.Ticket.Feature.Property.Address,
            Subject = t.Ticket.Subject,
            DateScheduled = String.Format("{0:MMMM dd, yyyy}", t.DateScheduled)
    }
);

Et voila, pas de boucles d'auto-référencement. 

Je réalise que cette situation peut ne pas être adéquate dans tous les cas, étant donné que les entités et les objets peuvent changer. Mais il vaut certainement la peine de réfléchir si tout le reste échoue.

11
pimbrouwers

Quelles que soient les classes ayant la référence d'une autre classe, ajoutez simplement un attribut comme celui-ci

[Newtonsoft.Json.JsonIgnoreAttribute]
public virtual ICollection<PurchaseOrderLine> Lines { get; set; }

Maintenant tout fonctionne bien

1
Ali Adravi

Dans votre classe DbContext, ajoutez cette ligne de code:

this.Configuration.ProxyCreationEnabled = false;

Par exemple:

public partial class EmpDBEntities : DbContext
{
    public EmpDBEntities()
        : base("name=EmpDBEntities")
    {
        this.Configuration.ProxyCreationEnabled = false;
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        throw new UnintentionalCodeFirstException();
    }

    public virtual DbSet<Department> Departments { get; set; }
    public virtual DbSet<Employee> Employees { get; set; }
}
1
Abdi

J'ai eu la même erreur, mais je l'ai vue à la fois sur le serveur de production et localement. Changer la configuration de DbContext n'a pas tout à fait résolu mon problème. Une solution différente m’a été présentée avec le

[IgnoreDataMember]

attribut sur les références d'entité DB. Voir le post ici si cela semble plus pertinent pour votre problème.

Erreur JSON sérialisée API Web ASP.NET: "boucle d'auto-référencement"

1
Freestyle076

J'ai eu le même problème et je l'ai résolu en décochant Json.NET dans les extensions de projet du gestionnaire de référence.

(voir l'image http://i.stack.imgur.com/RqbXZ.png )

J'ai également dû modifier le fichier project.csproj pour mapper le chemin correct de la nouvelle version:

<Reference Include="Newtonsoft.Json">
  <HintPath>..\packages\Newtonsoft.Json.6.0.5\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>

et encore dû configurer le web.config

  <dependentAssembly>
    <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
  </dependentAssembly>

Notez que dans le fichier web.config, j'ai été forcé de faire référence à la version OLDER (6.0.0.0) bien que la version installée soit 6.0.5.

J'espère que ça aide!

0
Marco

La référence circulaire se produit parce que vous utilisez un chargement rapide sur l’objet.

Vous avez 3 méthodes:

  • Désactivez le chargement rapide lors du chargement de votre requête (linq ou lambda) DbContext.Configuration.ProxyCreationEnabled = false;
  • Détachez les objets (= pas de fonctionnalité de chargement rapide ni de proxy)
    • Repository.Detach (entityObject)
    • DbContext.Entry (entityObject) .EntityState = EntityState.Detached
  • Cloner les propriétés
    • Vous pouvez utiliser quelque chose comme AutoMapper pour cloner l'objet, n'utilisez pas l'interface ICloneable, car elle clone également les propriétés de proxy dans l'objet, pour que cela ne fonctionne pas.
  • Si vous construisez une API, essayez d’utiliser un projet separte avec une configuration différente (qui ne renvoie pas de mandataires).

PS. Les proxies sont l'objet créé par EF lorsque vous le chargez à partir d'Entity Framework. En bref: cela signifie qu'il contient les valeurs d'origine et les valeurs mises à jour afin qu'elles puissent être mises à jour ultérieurement. Il gère d'autres choses à ;-)

0
NicoJuicy

J'avais le même problème, ce que j'ai fait est de ne transmettre que la colonne nécessaire à afficher, dans mon cas seulement 2.

 List<SubCategory> lstSubCategory = GetSubCateroy() // list from repo

 var subCategoryToReturn = lstSubCategory.Select(S => new { Id  = S.Id, Name = S.Name }); 

return this.Json(subCategoryToReturn , JsonRequestBehavior.AllowGet);
0
BJ Patel