Entity framework self referencing loop detected [duplicate]

独自空忆成欢 提交于 2019-12-17 02:27:04

问题


I have a strange error. I'm experimenting with a .NET 4.5 Web API, Entity Framework and MS SQL Server. I've already created the database and set up the correct primary and foreign keys and relationships.

I've created a .edmx model and imported two tables: Employee and Department. A department can have many employees and this relationship exists. I created a new controller called EmployeeController using the scaffolding options to create an API controller with read/write actions using Entity Framework. In the wizard, selected Employee as the model and the correct entity for the data context.

The method that is created looks like this:

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

When I call my API via /api/Employee, I get this error:

The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; ...System.InvalidOperationException","StackTrace":null,"InnerException":{"Message":"An error has occurred.","ExceptionMessage":"Self referencing loop detected with type 'System.Data.Entity.DynamicProxies.Employee_5D80AD978BC68A1D8BD675852F94E8B550F4CB150ADB8649E8998B7F95422552'. Path '[0].Department.Employees'.","ExceptionType":"Newtonsoft.Json.JsonSerializationException","StackTrace":" ...

Why is it self referencing [0].Department.Employees? That doesn't make a whole lot of sense. I would expect this to happen if I had circular referencing in my database but this is a very simple example. What could be going wrong?


回答1:


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.




回答2:


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



回答3:


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.




回答4:


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.




回答5:


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



回答6:


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



回答7:


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



回答8:


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



回答9:


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



回答10:


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.




回答11:


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




回答12:


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



回答13:


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



回答14:


DTO details

public class TreeItem {
    public string text;
    public string value;
    public string Checked;
    public string Collapsed;
    public List<TreeItem> children;
}

public IActionResult TreeViewStructure() {
    List<TreeItem> treeView = new List<TreeItem>(); 
    var parentItems = _sectionService.GetBy(sec => sec.ParentSectionId == null);
    foreach (var item in parentItems) {
        treeView.Add(new TreeItem { text = item.SectionName, value = item.SectionId.ToString(),children= ChildTreeItems(item) });
        //var childItems=ChildItems(item,item.ParentSectionId);
    }
    return Ok(treeView);
}

public List<TreeItem> ChildTreeItems(Section treeView) {
    List<TreeItem> ChildItems = new List<TreeItem>();
    var childItem = _sectionService.GetBy(sec => sec.ParentSectionId == treeView.SectionId);
    if (childItem.Count > 0) {
        foreach (var item in childItem) {
            ChildItems.Add(new TreeItem { text = item.SectionName, value = item.SectionId.ToString(), children = ChildTreeItems(item) });
        }
        return ChildItems;
    } else {
        return ChildItems;
    }
}


来源:https://stackoverflow.com/questions/19467673/entity-framework-self-referencing-loop-detected

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!