111

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?

4

13 回答 13

185

那么基于 Json.net 的默认 Json 格式化程序的正确答案是设置ReferenceLoopHandlingIgnore.

只需将其添加到Application_StartGlobal.asax 中:

HttpConfiguration config = GlobalConfiguration.Configuration;

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

这是正确的方法。它将忽略指向对象的引用。

其他响应侧重于通过排除数据或制作外观对象来更改返回的列表,有时这不是一个选项。

使用JsonIgnore属性来限制引用可能会很耗时,而且如果您想从另一个点开始序列化树,这将是一个问题。

于 2015-05-13T00:06:36.747 回答
55

发生这种情况是因为您尝试直接序列化 EF 对象集合。由于部门与员工和员工与部门有关联,因此 JSON 序列化程序将无限循环读取 d.Employee.Departments.Employee.Departments 等...

要在序列化之前解决此问题,请使用您想要的道具创建一个匿名类型

示例(伪)代码:

departments.select(dep => new { 
    dep.Id, 
    Employee = new { 
        dep.Employee.Id, dep.Employee.Name 
    }
});
于 2013-10-19T15:29:54.050 回答
36

我遇到了同样的问题,发现您可以将该[JsonIgnore]属性应用于您不想被序列化的导航属性。它仍然会序列化父实体和子实体,但只是避免了自引用循环。

于 2014-02-24T22:16:36.330 回答
21

我知道这个问题已经很老了,但它仍然很受欢迎,我看不到 ASP.net Core 的任何解决方案。

我是ASP.net Core的情况,你需要JsonOutputFormatterStartup.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));
        });

        //...
    }

实现之后,JSON 序列化器将简单地忽略循环引用。这意味着:它将返回 null 而不是无限加载相互引用的对象。

如果没有上述解决方案,请使用:

var employees = db.Employees.ToList();

将加载Employees并与它们相关Departments

设置ReferenceLoopHandlingIgnore,Departments后将设置为 null ,除非您将其包含在查询中:

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

另外,请记住,它将清除所有OutputFormatters,如果您不想这样做,可以尝试删除此行:

options.OutputFormatters.Clear();

self referencing loop但是由于某种原因,在我的情况下删除它会再次导致异常。

于 2017-12-09T12:41:29.767 回答
9

主要问题是序列化与其他实体模型有关系的实体模型(外键关系)。这种关系会导致自引用,这将在序列化为 json 或 xml 时引发异常。有很多选择。无需使用自定义模型序列化实体模型。使用 Automapper 或 Valueinjector 映射到自定义模型(对象映射)的实体模型数据中的值或数据然后返回请求,它将序列化而没有任何其他问题。或者您可以序列化实体模型,因此首先禁用实体模型中的代理

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

要在 XML 中保留对象引用,您有两种选择。更简单的选择是将 [DataContract(IsReference=true)] 添加到您的模型类中。IsReference 参数启用对象引用。请记住,DataContract 选择加入序列化,因此您还需要将 DataMember 属性添加到属性中:

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

global.asax 中的 Json 格式

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

以xml格式

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Employee), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer<Employee>(dcs);
于 2013-10-19T16:26:08.897 回答
8

消息错误意味着您有一个自引用循环。

您生成的 json 类似于此示例(包含一名员工的列表):

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

]

当您请求某些内容时,您必须告诉数据库上下文您不想获取所有链接实体。DbContext 的选项是Configuration.LazyLoadingEnabled

我发现最好的方法是为序列化创建一个上下文:

public class SerializerContext : LabEntities 
{
    public SerializerContext()
    {
        this.Configuration.LazyLoadingEnabled = false;
    }
}
于 2014-09-01T08:04:50.527 回答
8

Configuration.ProxyCreationEnabled = false;在上下文模型部分类定义的构造函数中添加一行。

    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)
    {
    }
}
于 2017-06-23T07:32:51.703 回答
6

我只有一个我想使用的模型,所以我最终得到了以下代码:

var JsonImageModel = Newtonsoft.Json.JsonConvert.SerializeObject(Images, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
于 2017-11-01T18:15:51.103 回答
5

如果您尝试在 Blazor (ASP.NET Core Hosted) 模板中更改此设置,则需要将以下内容传递给项目中的AddNewtonsoftJson调用:Startup.csServer

services.AddMvc().AddNewtonsoftJson(options => 
    options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore
);
于 2019-05-09T12:56:29.547 回答
4

我刚刚在我的 .net 核心站点上遇到了同样的问题。接受的答案对我不起作用,但我发现 ReferenceLoopHandling.Ignore 和 PreserveReferencesHandling.Objects 的组合修复了它。

//serialize item
var serializedItem = JsonConvert.SerializeObject(data, Formatting.Indented, 
new JsonSerializerSettings
{
     PreserveReferencesHandling = PreserveReferencesHandling.Objects,
     ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
于 2019-06-18T07:32:31.547 回答
0

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

于 2014-07-23T21:27:59.467 回答
0

以自引用为例

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);
于 2018-07-09T07:54:30.753 回答
0

我知道这是一个老问题,但这是我在自己的代码中发现的一个非常相似的编码问题的解决方案:

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

由于人员对象包含人员表中的所有内容,而职位对象包含具有该职位的人员列表,因此数据库保持自引用。我认为禁用代理创建和延迟加载可以解决这个问题,但不幸的是它没有。

对于那些无法做到这一点,请尝试上面的解决方案。为自引用的每个对象显式创建一个新对象,但省略对象列表或返回到前一个实体的对象将修复它,因为禁用延迟加载似乎对我不起作用。

于 2019-10-09T14:42:41.083 回答