EF 4.1 – Código Primero – Error de serialización de referencia circular JSON

Estoy obteniendo un Error de serialización de referencia circular aunque, que yo sepa, no tengo ninguna referencia circular. Estoy recuperando un conjunto de pedidos de la base de datos y enviándolos al cliente como JSON. Todo el código se muestra a continuación.

Este es el error:

Error

Se detectó una referencia circular al serializar un objeto de tipo ‘System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812’. Descripción: se produjo una excepción no controlada durante la ejecución de la solicitud web actual. Revise el seguimiento de la stack para obtener más información sobre el error y dónde se originó en el código.

Detalles de la excepción: System.InvalidOperationException: se detectó una referencia circular al serializar un objeto de tipo ‘System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812’.

Error de fuente:

Se generó una excepción no controlada durante la ejecución de la solicitud web actual. La información sobre el origen y la ubicación de la excepción se puede identificar utilizando el seguimiento de stack de excepción a continuación.

Mis clases son las siguientes:

Orden

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; } } 

Paciente

 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; } } 

Período de certificación

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

Agencia

 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; } } 

Diagnóstico

 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; } } 

Usuario

 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; } } 

NOTA: LA CLASE DE DIRECCIONES ES UNA NUEVA ADICIÓN DURANTE LA EDICIÓN

Dirección

 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; } } 

El código que ejecuta la serialización está aquí:

Extracto de OrderController

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

Gracias

Podría intentar eliminar la palabra clave virtual de todas las propiedades de navegación para deshabilitar la carga diferida y la creación de proxy, y luego usar la carga ansiosa para cargar el gráfico de objeto requerido explícitamente:

 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 referencia a su publicación anterior , parece que su aplicación no se basa en la carga diferida de todos modos, ya que introdujo allí las propiedades virtuales para cargar el gráfico del objeto de forma perezosa, lo que posiblemente causa el problema de serialización.

Editar

No es necesario eliminar la palabra clave virtual de las propiedades de navegación (lo que haría que la carga diferida sea completamente imposible para el modelo). Es suficiente para desactivar la creación de proxy (que también deshabilita la carga lenta) para las circunstancias específicas en que los proxies son molestos, como la serialización:

 ppEFContext.Configuration.ProxyCreationEnabled = false; 

Esto desactiva la creación de proxy solo para la instancia de contexto específica ppEFContext .

(Acabo de ver, @WillC ya lo mencioné aquí. Por favor vote a favor de esta edición a su respuesta.)

Cuando sepa que necesita serializar desde un contexto particular, puede deshabilitar la creación del proxy para esa consulta en particular, como se muestra a continuación. Esto funcionó para mí y fue mejor que revisar mis clases modelo.

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

Este enfoque elimina el tipo de objeto proxy para esta instancia particular del contexto, por lo que los objetos devueltos son la clase real y, por lo tanto, la serialización no es un problema.

es decir:

 {Models.car} 

en lugar de

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

El problema es que estás realmente serializando un objeto proxy generado por el marco de entidad. Desafortunadamente, esto tiene algunos problemas cuando se usa con el serializador JSON. Puede considerar asignar sus entidades a clases de POCO simples especiales por el bien de la compatibilidad con JSON.

Hay un atributo para agregar a los objetos de Entity Framework

 [ScriptIgnore] 

Esto hace que el código no realice referencias circulares.

Creo que han arreglado esto en la última versión.

Consulte los documentos de ayuda en la sección ” Serialización y deserialización de JSON -> Serialización y preservación de referencias de objetos “.

Establezca esta configuración al inicializar el Serializador JSON.Net:

 PreserveReferencesHandling = PreserveReferencesHandling.Objects; 

Entonces un ejemplo sería este:

 var serializerSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects }; string json = JsonConvert.SerializeObject(people, Formatting.Indented, serializerSettings); 

Verifiqué que esto funciona con mi primera solución de código y una referencia circular en las propiedades de navegación. Si observa el JSON resultante, debería tener las propiedades “$ id” y “$ ref” en todas partes.

Una solución alternativa sería usar tipos anónimos como resultado de una consulta LINQ.

En mi proyecto, estoy usando la carga diferida extensivamente, y deshabilitarlo no era lo correcto.

Una solución alternativa, si solo se necesitan algunos valores de los objetos, es crear una clase anónima y devolverla, como en el siguiente ejemplo:

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

La referencia circular ocurre porque usa carga ansiosa en el objeto.

Tienes un par de métodos:

  • Desactive la carga ansiosa al cargar su consulta (linq o lambda) DbContext.Configuration.ProxyCreationEnabled = false;
  • Eliminar la palabra clave virtual del modelo de dominio
  • Separar los objetos (= sin funcionalidad de carga ansiosa y sin proxy)
    • Repository.Detach (entityObject)
    • DbContext.Entry (entityObject) .EntityState = EntityState.Detached
  • Clona las propiedades
    • Puede usar algo como AutoMapper para clonar el objeto, no use la interfaz ICloneable, ya que también clona ProxyProperties en el objeto, por lo que no funcionará.
  • En caso de que esté creando una API, intente utilizar un proyecto separte con una configuración diferente (que no arroje proxies)

PD. Proxies es el objeto creado por EF cuando lo carga desde Entity Framework. En resumen: Significa que contiene los valores originales y los valores actualizados para que puedan actualizarse más tarde. Maneja otras cosas para 😉

Para aquellos que usan las clases proxy EF / Linq2SQL mi solución fue simplemente eliminar la referencia padre en las entidades de mi hijo.

Entonces, en mi modelo, seleccioné la relación y cambié la referencia principal para que sea interna en lugar de pública.

Puede que no sea una solución ideal para todos, pero funcionó para mí.

Puede eliminar la palabra clave virtual :

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

Tenga en cuenta que cuando elimina la palabra clave virtual, la carga diferida se desactivará.