c# - net - Se ha detectado un bucle de autorreferenciado en el marco de la entidad
web api c# entity framework (10)
Tengo un extraño error Estoy experimentando con .NET 4.5 Web API, Entity Framework y MS SQL Server. Ya creé la base de datos y configuré las claves y relaciones primarias y externas correctas.
Creé un modelo .edmx e importé dos tablas: Empleado y Departamento. Un departamento puede tener muchos empleados y esta relación existe. Creé un nuevo controlador llamado EmployeeController utilizando las opciones de andamios para crear un controlador API con acciones de lectura / escritura utilizando Entity Framework. En el asistente, seleccione Empleado como modelo y la entidad correcta para el contexto de datos.
El método que se crea tiene este aspecto:
public IEnumerable<Employee> GetEmployees()
{
var employees = db.Employees.Include(e => e.Department);
return employees.AsEnumerable();
}
Cuando llamo a mi API a través de / api / Employee, obtengo este error:
El tipo ''ObjectContent`1'' no pudo serializar el cuerpo de la respuesta para el tipo de contenido ''application / json; ... System.InvalidOperationException "," StackTrace ": null," InnerException ": {" Message ":" Ha ocurrido un error. "," ExceptionMessage ":" Loop de autoreferencia detectado con el tipo ''System.Data.Entity.DynamicProxies .Employee_5D80AD978BC68A1D8BD675852F94E8B550F4CB150ADB8649E8998B7F95422552 ''. Ruta ''[0] .Department.Employees''. "," ExceptionType ":" Newtonsoft.Json.JsonSerializationException "," StackTrace ":" ...
¿Por qué se auto referencia [0] .Departamento.Empleados? Eso no tiene mucho sentido. Esperaría que esto sucediera si tuviera referencias circulares en mi base de datos, pero este es un ejemplo muy simple. ¿Qué podría estar yendo mal?
Agregue una línea Configuration.ProxyCreationEnabled = false;
en el constructor de su definición de clase parcial del modelo de contexto.
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)
{
}
}
Bueno, la respuesta correcta para el formateador Json predeterminado basado en Json.net es establecer ReferenceLoopHandling
para Ignore
.
Solo agregue esto a Application_Start
en Global.asax:
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter
.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
Esta es la forma correcta. Ignorará la referencia que apunta nuevamente al objeto.
Otras respuestas se centraron en cambiar la lista que se devuelve mediante la exclusión de datos o al hacer un objeto fachada y, a veces, eso no es una opción.
Usar el atributo JsonIgnore
para restringir las referencias puede llevar mucho tiempo y si desea serializar el árbol comenzando desde otro punto, eso será un problema.
El mensaje de error significa que tiene un bucle de auto referencia.
El JSON que produces es como este ejemplo (con una lista de un empleado):
[
employee1 : {
name: "name",
department : {
name: "departmentName",
employees : [
employee1 : {
name: "name",
department : {
name: "departmentName",
employees : [
employee1 : {
name: "name",
department : {
and again and again....
}
]
}
}
]
}
}
]
Debe decirle al contexto db que no desea obtener todas las entidades vinculadas cuando solicita algo. La opción para DbContext es Configuration.LazyLoadingEnabled
La mejor forma que encontré es crear un contexto para la serialización:
public class SerializerContext : LabEntities
{
public SerializerContext()
{
this.Configuration.LazyLoadingEnabled = false;
}
}
El principal problema es que se serializa un modelo de entidad que tiene relación con otro modelo de entidad (relación de clave externa). Esta relación causa la auto referencia, esto arrojará una excepción mientras se serializa a json o xml. Hay muchas opciones. Sin serializar modelos de entidades mediante el uso de modelos personalizados. Los valores o datos de los datos del modelo de entidad asignados a modelos personalizados (mapeo de objetos) usando Automapper o Valueinjector luego devuelven la solicitud y se serializarán sin ningún otro problema. O puede serializar el modelo de entidad, primero, deshabilite los proxies en el modelo de entidad
public class LabEntities : DbContext
{
public LabEntities()
{
Configuration.ProxyCreationEnabled = false;
}
Para conservar referencias de objetos en XML, tiene dos opciones. La opción más simple es agregar [DataContract (IsReference = true)] a su clase de modelo. El parámetro IsReference habilita referencias de objeto. Recuerde que DataContract hace que la serialización sea opt-in, por lo que también necesitará agregar atributos de DataMember a las propiedades:
[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;}
}
En formato Json en global.asax
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.All;
en formato xml
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Employee), null, int.MaxValue,
false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer<Employee>(dcs);
Esto sucede porque estás intentando serializar la colección de objetos EF directamente. Dado que el departamento tiene una asociación con el empleado y el empleado con el departamento, el serializador JSON realizará un ciclo infinito de lectura d.Empleo.Departamentos.Empleados.Departamentos, etc ...
Para solucionar este problema antes de la serialización, crea un tipo anónimo con los accesorios que quieras
código de ejemplo (psuedo):
departments.select(dep => new {
dep.Id,
Employee = new {
dep.Employee.Id, dep.Employee.Name
}
});
Solo tenía un modelo que quería usar, así que terminé con el siguiente código:
var JsonImageModel = Newtonsoft.Json.JsonConvert.SerializeObject(Images, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
Soy consciente de que la pregunta es bastante antigua, pero sigue siendo popular y no veo ninguna solución para ASP.net Core.
En el caso de ASP.net Core , necesita agregar el nuevo JsonOutputFormatter
en el archivo 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));
});
//...
}
Después de implementarlo, el serializador JSON simplemente ignorará las referencias de bucle. Lo que significa es que devolverá nulo en lugar de cargar objetos infinitamente haciendo referencia entre sí.
Sin la solución anterior que usa:
var employees = db.Employees.ToList();
Cargaría Employees
y Departments
relacionados con ellos.
Después de configurar ReferenceLoopHandling
para Ignore
, Departments
se establecerá en null a menos que lo incluya en su consulta:
var employees = db.Employees.Include(e => e.Department);
Además, tenga en cuenta que borrará todos OutputFormatters , si no desea puede intentar eliminar esta línea:
options.OutputFormatters.Clear();
Pero eliminarlo causa nuevamente self referencing loop
excepción de self referencing loop
en mi caso por algún motivo.
También podría considerar la posibilidad de agregar muestras explícitas para cada controlador / acción, como se describe aquí:
es decir, config.SetActualResponseType (typeof (SomeType), "Values", "Get");
Tuve el mismo problema y descubrí que puede aplicar el atributo [JsonIgnore]
a la propiedad de navegación que no desea serializar. Seguirá serializando las entidades padre e hijo pero solo evita el bucle de auto referencia.
autorreferencia como ejemplo
=============================================== ===========
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);