web-dev-qa-db-fra.com

EF 4.1 - Code First - Erreur de sérialisation de référence circulaire JSON

Je reçois une erreur de sérialisation de référence circulaire bien que, à ma connaissance, je n'ai pas de référence circulaire. Je récupère un ensemble de commandes de la base de données et les envoie au client en tant que JSON. Tout le code est montré ci-dessous.

Voici l'erreur:

Erreur

Une référence circulaire a été détectée lors de la sérialisation d'un objet de type 'System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812'. Description: une exception non gérée s'est produite lors de l'exécution de la demande Web en cours. Veuillez consulter la trace de la pile pour plus d'informations sur l'erreur et son origine dans le code.

Détails de l'exception: System.InvalidOperationException: une référence circulaire a été détectée lors de la sérialisation d'un objet de type 'System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812'.

Erreur source:

Une exception non gérée a été générée lors de l'exécution de la demande Web actuelle. Les informations concernant l'origine et l'emplacement de l'exception peuvent être identifiées à l'aide de la trace de pile d'exceptions ci-dessous.

Mes cours sont les suivants:

Commande

public class Order
{
    [Key]
    public int OrderId { get; set; }

    public int PatientId { get; set; }
    public virtual Patient Patient { get; set; }

    public int CertificationPeriodId { get; set; }
    public virtual CertificationPeriod CertificationPeriod { get; set; }

    public int AgencyId { get; set; }
    public virtual Agency Agency { get; set; }

    public int PrimaryDiagnosisId { get; set; }
    public virtual Diagnosis PrimaryDiagnosis { get; set; }

    public int ApprovalStatusId { get; set; }
    public virtual OrderApprovalStatus ApprovalStatus { get; set; }

    public int ApproverId { get; set; }
    public virtual User Approver { get; set; }

    public int SubmitterId { get; set; }
    public virtual User Submitter { get; set; }

    public DateTime ApprovalDate { get; set; }

    public DateTime SubmittedDate { get; set; }
    public Boolean IsDeprecated { get; set; }
}

Patient

public class Patient
{
    [Key]
    public int PatientId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string MiddleInitial { get; set; }
    public bool IsMale;
    public DateTime DateOfBirth { get; set; }

    public int PatientAddressId { get; set; }
    public Address PatientAddress { get; set; }

    public bool IsDeprecated { get; set; }
}

Période de certification

public class CertificationPeriod
{
    [Key]
    public int CertificationPeriodId { get; set; }
    public DateTime startDate { get; set; }
    public DateTime endDate { get; set; }
    public bool isDeprecated { get; set; }
}

Agence

public class Agency
{
    [Key]
    public int AgencyId { get; set; }
    public string Name { get; set; }

    public int PatientAddressId { get; set; }
    public virtual Address Address { get; set; }
}

Diagnostic

public class Diagnosis
{
    [Key]
    public int DiagnosisId { get; set; }
    public string Icd9Code { get; set; }
    public string Description { get; set; }
    public DateTime DateOfDiagnosis { get; set; }
    public string Onset { get; set; }
    public string Details { get; set; }
}

OrderApprovalStatus

public class OrderApprovalStatus
{
    [Key]
    public int OrderApprovalStatusId { get; set; }
    public string Status { get; set; }
}

tilisateur

public class User
{
    [Key]
    public int UserId { get; set; }
    public string Login { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string NPI { get; set; }
    public string Email { get; set; }

}

REMARQUE: CLASSE D'ADRESSE IS NOUVELLE ADDITION PENDANT LA MODIFICATION

Adresse

public class Address
{
    [Key]
    public int AddressId { get; set; }
    public string StreetAddress { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
    public string Phone { get; set; }
    public string Title { get; set; }
    public string Label { get; set; }
}

Le code qui exécute la sérialisation est ici:

Extrait de OrderController

    public ActionResult GetAll()
    {
        return Json(ppEFContext.Orders, JsonRequestBehavior.AllowGet);
    }

Merci

47
Guido Anselmi

Vous pouvez essayer de supprimer le mot clé virtual de toutes les propriétés de navigation pour désactiver le chargement paresseux et la création de proxy, puis utiliser un chargement rapide à la place pour charger explicitement le graphique d'objet requis:

public ActionResult GetAll()
{
    return Json(ppEFContext.Orders
                           .Include(o => o.Patient)
                           .Include(o => o.Patient.PatientAddress)
                           .Include(o => o.CertificationPeriod)
                           .Include(o => o.Agency)
                           .Include(o => o.Agency.Address)
                           .Include(o => o.PrimaryDiagnosis)
                           .Include(o => o.ApprovalStatus)
                           .Include(o => o.Approver)
                           .Include(o => o.Submitter),
        JsonRequestBehavior.AllowGet);
}

En vous référant à votre article précédent il semble que votre application ne s'appuie pas sur le chargement paresseux de toute façon parce que vous y avez introduit les propriétés virtuelles pour charger le graphique d'objet paresseusement, ce qui peut éventuellement causer des problèmes de sérialisation.

Modifier

Il n'est pas nécessaire de supprimer le mot clé virtual des propriétés de navigation (ce qui rendrait le chargement paresseux complètement impossible pour le modèle). Il suffit de désactiver la création de proxy (qui désactive également le chargement paresseux) dans les circonstances spécifiques où les proxy sont perturbants, comme la sérialisation:

ppEFContext.Configuration.ProxyCreationEnabled = false;

Cela désactive la création de proxy uniquement pour l'instance de contexte spécifique ppEFContext.

(Je viens de voir, @WillC l'a déjà mentionné ici. Votez pour cette modification s'il vous plaît à sa réponse.)

48
Slauma

Lorsque vous savez que vous devez sérialiser à partir d'un contexte particulier, vous pouvez désactiver la création de proxy pour cette requête particulière comme ci-dessous. Cela a fonctionné pour moi et c'était mieux que de réviser mes cours de modèle.

using (var context = new MeContext())
{
    context.Configuration.ProxyCreationEnabled = false;
    return context.cars.Where(w => w.Brand == "Ferrari")
}

Cette approche supprime le type d'objet proxy pour cette instance particulière du contexte, de sorte que les objets renvoyés sont la classe réelle et donc la sérialisation n'est pas un problème.

c'est à dire:

{Models.car} 

au lieu de

{System.Data.Entity.DynamicProxies.car_231710A36F27E54BC6CE99BB50E0FE3B6BD4462EC‌​A19695CD1BABB79605296EB} 
41
WillC

Le problème est que vous sérialisez réellement un objet proxy généré par le framework d'entité. Malheureusement, cela a quelques problèmes lorsqu'il est utilisé avec le sérialiseur JSON. Vous pourriez envisager de mapper vos entités à des classes POCO simples et spéciales pour des raisons de compatibilité JSON.

9
Christoph

Il existe un attribut à ajouter aux objets Entity Framework

[ScriptIgnore]

Cela rend le code n'effectue pas de références circulaires.

8
bdparrish

Je pense qu'ils ont corrigé cela dans la dernière version.

Consultez les documentation d'aide dans la section " Sérialisation et désérialisation JSON -> Sérialisation et préservation des références d'objets ".

Définissez ce paramètre lors de l'initialisation du sérialiseur JSON.Net:

PreserveReferencesHandling = PreserveReferencesHandling.Objects;

Donc, un exemple serait ceci:

var serializerSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects };

string json = JsonConvert.SerializeObject(people, Formatting.Indented, serializerSettings);

J'ai vérifié que cela fonctionne avec ma première solution de code et une référence circulaire dans les propriétés de navigation. Si vous regardez le JSON résultant, il devrait avoir des propriétés "$ id" et "$ ref" partout.

7
John Bubriski

Une solution alternative serait de tiliser des types anonymes comme résultat d'une requête LINQ.

Dans mon projet, j'utilise beaucoup le chargement paresseux et le désactiver n'était pas la bonne chose à faire.

6
abdelkarim

Une solution alternative, si seulement quelques valeurs d'objets sont nécessaires, est de construire une classe anonyme et de la renvoyer, comme dans l'exemple ci-dessous:

public JsonResult AjaxFindByName(string term)
{
    var customers = context.Customers
        .Where(c => c.Name.ToUpper().Contains(term.ToUpper())).Take(10)
        .AsEnumerable()
        .Select(c => new { 
            value = c.Name, 
            SSN = String.Format(@"{0:000\-00\-0000}", c.SSN),
            CustomerID = c.CustomerID });

    return Json(customers, JsonRequestBehavior.AllowGet);
}
3

La référence circulaire se produit car vous utilisez un chargement rapide sur l'objet.

Vous avez deux méthodes:

  • Désactivez le chargement rapide lorsque vous chargez votre requête (linq ou lambda) DbContext.Configuration.ProxyCreationEnabled = false;
  • Supprimez le mot-clé virtuel du modèle de domaine
  • Détachez les objets (= pas de fonctionnalité de chargement rapide et pas de proxy)
    • Repository.Detach (entityObject)
    • DbContext.Entry (entityObject) .EntityState = EntityState.Detached
  • Clonez les propriétés
    • Vous pouvez utiliser quelque chose comme AutoMapper pour cloner l'objet, n'utilisez pas l'interface ICloneable, car il clone également les propriétés proxy dans l'objet, donc cela ne fonctionnera pas.
  • Dans le cas où vous construisez une API, essayez d'utiliser un projet separte avec une configuration différente (qui ne renvoie pas de proxy)

PS. Les proxys sont l'objet créé par EF lorsque vous le chargez depuis 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 plus tard. Il gère d'autres choses pour ;-)

2
NicoJuicy

Vous pouvez supprimer le mot clé virtual:

public virtual Patient Patient { get; set; } -> public Patient Patient { get; set; }

Gardez à l'esprit que lorsque vous supprimez le mot clé virtuel, le chargement paresseux sera désactivé.

0
Rabolf
0
steveareeno

Pour ceux qui utilisent les classes proxy EF/Linq2SQL, ma solution était de simplement supprimer la référence parent sur mes entités enfants.

Donc, dans mon modèle, j'ai sélectionné la relation et changé la référence parent pour qu'elle soit interne plutôt que publique.

Peut ne pas être une solution idéale pour tous, mais a fonctionné pour moi.

0
Anthony Main