检测到实体框架自引用循环

我有一个奇怪的错误。我在试验一种。NET 4.5 Web API、实体框架和 MS SQL Server。我已经创建了数据库,并设置了正确的主键和外键以及关系。

我创造了一个。Edmx 模型并导入了两个表: Employee 和 Department。一个部门可以有很多员工,这种关系是存在的。我使用脚手架选项创建了一个名为 EmployeeController 的新控制器,该控制器使用 Entity Framework 创建了一个具有读/写操作的 API 控制器。在向导中,选择 Employee 作为数据上下文的模型和正确的实体。

创建的方法如下所示:

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

当我通过/API/Employee 调用 API 时,会得到这个错误:

“ ObjectContent‘1”类型未能序列化内容类型“ application/json; ... System”的响应正文。InvalidOperationException”,“ StackTrace”: null,“ InnerException”: {“ Message”: “已发生错误。”,“ ExceptionMessage”: “使用‘ System 类型检测到自引用循环。百科。实体。DynamicProxies 动态代理。Employee _ 5D80AD978BC68A1D8BD675852F94E8B550F4CB150ADB8649E8998B7F95422552’。路径’[0]。税务局。雇员」例外类型: 牛顿软件。杰森。“ JsonSerializationException”,“ StackTrace”: “ ..。

为什么它自我引用[0]。税务局。员工?这说不通啊。如果我的数据库中有循环引用,我希望这种情况会发生,但这是一个非常简单的例子。会出什么问题呢?

127969 次浏览

This happens because you're trying to serialize the EF object collection directly. Since department has an association to employee and employee to department, the JSON serializer will loop infinetly reading d.Employee.Departments.Employee.Departments etc...

To fix this right before the serialization create an anonymous type with the props you want

example (psuedo)code:

departments.select(dep => new {
dep.Id,
Employee = new {
dep.Employee.Id, dep.Employee.Name
}
});

The main problem is that serializing an entity model which has relation with other entity model(Foreign key relationship). This relation causes self referencing this will throw exception while serialization to json or xml. There are lots of options. Without serializing entity models by using custom models.Values or data from entity model data mapped to custom models(object mapping) using Automapper or Valueinjector then return request and it will serialize without any other issues. Or you can serialize entity model so first disable proxies in entity model

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

To preserve object references in XML, you have two options. The simpler option is to add [DataContract(IsReference=true)] to your model class. The IsReference parameter enables oibject references. Remember that DataContract makes serialization opt-in, so you will also need to add DataMember attributes to the properties:

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

In Json format in global.asax

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

in xml format

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

I had same problem and found that you can just apply the [JsonIgnore] attribute to the navigation property you don't want to be serialised. It will still serialise both the parent and child entities but just avoids the self referencing loop.

I might also look into adding explicit samples for each controller/action, as well covered here:

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

i.e. config.SetActualResponseType(typeof(SomeType), "Values", "Get");

The message error means that you have a self referencing loop.

The json you produce is like this example (with a list of one employee) :

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

]

You have to tell the db context that you don't want to get all linked entities when you request something. The option for DbContext is Configuration.LazyLoadingEnabled

The best way I found is to create a context for serialization :

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

Well the correct answer for the default Json formater based on Json.net is to set ReferenceLoopHandling to Ignore.

Just add this to the Application_Start in Global.asax:

HttpConfiguration config = GlobalConfiguration.Configuration;


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

This is the correct way. It will ignore the reference pointing back to the object.

Other responses focused in changing the list being returned by excluding data or by making a facade object and sometimes that is not an option.

Using the JsonIgnore attribute to restrict the references can be time consuming and if you want to serialize the tree starting from another point that will be a problem.

Add a line Configuration.ProxyCreationEnabled = false; in constructor of your context model partial class definition.

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

I only had one model i wanted to use, so i ended up with the following code:

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

I'm aware that question is quite old, but it's still popular and I can't see any solution for ASP.net Core.

I case of ASP.net Core, you need to add new JsonOutputFormatter in Startup.cs file:

    public void ConfigureServices(IServiceCollection services)
{


services.AddMvc(options =>
{
options.OutputFormatters.Clear();
options.OutputFormatters.Add(new JsonOutputFormatter(new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
}, ArrayPool<char>.Shared));
});


//...
}

After implementing it, JSON serializer will simply ignore loop references. What it means is: it will return null instead of infinitely loading objects referencing each other.

Without above solution using:

var employees = db.Employees.ToList();

Would load Employees and related to them Departments.

After setting ReferenceLoopHandling to Ignore, Departments will be set to null unless you include it in your query:

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

Also, keep in mind that it will clear all OutputFormatters, if you don't want that you can try removing this line:

options.OutputFormatters.Clear();

But removing it causes again self referencing loop exception in my case for some reason.

Self-referencing as example

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

If you are trying to change this setting in the Blazor (ASP.NET Core Hosted) template, you need to pass the following to the AddNewtonsoftJson call in Startup.cs in the Server project:

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

I just had the same issue on my .net core site. The accepted answer didn't work for me but i found that a combination of ReferenceLoopHandling.Ignore and PreserveReferencesHandling.Objects fixed it.

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

I know this is an old question but here is the solution I found to a very similar coding issue in my own code:

var response = ApiDB.Persons.Include(y => y.JobTitle).Include(b => b.Discipline).Include(b => b.Team).Include(b => b.Site).OrderBy(d => d.DisplayName).ToArray();
foreach (var person in response)
{
person.JobTitle = new JobTitle()
{
JobTitle_ID = person.JobTitle.JobTitle_ID,
JobTitleName = person.JobTitle.JobTitleName,
PatientInteraction = person.JobTitle.PatientInteraction,
Active = person.JobTitle.Active,
IsClinical = person.JobTitle.IsClinical
};
}

Since the person object contains everything from the person table and the job title object contains a list of persons with that job title, the database kept self referencing. I thought disabling proxy creation and lazy loading would fix this but unfortunately it didn't.

For the that aren't able to do that, try the solution above. Explicitly creating a new object for each object that self references, but leave out the list of objects or object that goes back to the previous entity will fix it since disabling lazy loading does not appear to work for me.