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

时间:2013-10-19 15:26:20

标签: c# serialization entity-framework-4 asp.net-web-api

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

我创建了一个.edmx模型并导入了两个表:Employee和Department。一个部门可以有很多员工,这种关系存在。我使用scaffolding选项创建了一个名为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.Data.Entity.DynamicProxies检测到自引用循环.Employee_5D80AD978BC68A1D8BD675852F94E8B550F4CB150ADB8649E8998B7F95422552' 。 Path'[0] .Department.Employees'。“,”ExceptionType“:”Newtonsoft.Json.JsonSerializationException“,”StackTrace“:”...

为什么它自引用[0] .Department.Employees?这并没有多大意义。如果我在我的数据库中进行循环引用,我希望这会发生,但这是一个非常简单的例子。可能出现什么问题?

13 个答案:

答案 0 :(得分:142)

基于Json.net的默认Json格式化器的正确答案是将ReferenceLoopHandling设置为Ignore

只需将其添加到Global.asax中的Application_Start

HttpConfiguration config = GlobalConfiguration.Configuration;

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

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

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

使用JsonIgnore属性来限制引用可能非常耗时,并且如果要从另一个点开始序列化树,那将是一个问题。

答案 1 :(得分:49)

这是因为您尝试直接序列化EF对象集合。由于部门与员工和员工之间存在关联,因此JSON序列化程序将循环读取d.Employee.Departments.Employee.Departments等...

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

示例(伪)代码:

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

答案 2 :(得分:31)

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

答案 3 :(得分:15)

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

我是 ASP.net核心的情况,您需要在JsonOutputFormatter文件中添加新的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));
        });

        //...
    }

实现它之后,JSON序列化器将简单地忽略循环引用。它的含义是:它将返回null而不是无限地加载彼此引用的对象。

没有以上解决方案使用:

var employees = db.Employees.ToList();

会加载Employees并与Departments相关联。

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

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

此外,请注意,如果您不想要删除此行,则会清除所有OutputFormatters

options.OutputFormatters.Clear();

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

答案 4 :(得分:8)

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

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

要保留XML中的对象引用,您有两个选择。更简单的选项是将[DataContract(IsReference = true)]添加到模型类中。 IsReference参数启用oibject引用。请记住,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;}
}

以Json格式 在global.asax

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

答案 5 :(得分:7)

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

您生成的json就像这个例子(包含一名员工的名单):

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

您必须告诉db上下文,当您请求某些内容时,您不希望获得所有链接的实体。 DbContext的选项是Configuration.LazyLoadingEnabled

我找到的最好方法是创建序列化的上下文:

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

答案 6 :(得分:7)

在上下文模型部分类定义的构造函数中添加一行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)
    {
    }
}

答案 7 :(得分:3)

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

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

答案 8 :(得分:1)

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

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

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

对于无法执行的操作,请尝试上述解决方案。显式地为每个自我引用的对象创建一个新对象,但不包括对象列表或返回到先前实体的对象将解决此问题,因为禁用延迟加载对我而言似乎不起作用。

答案 9 :(得分:0)

我可能还会考虑为每个控制器/操作添加显式示例,这里也包含:

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

即。 config.SetActualResponseType(typeof(SomeType),“Values”,“Get”);

答案 10 :(得分: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);

答案 11 :(得分:0)

如果尝试在Blazor(ASP.NET Core托管)模板中更改此设置,则需要将以下内容传递给AddNewtonsoftJsonStartup.cs中的Server调用项目:

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

答案 12 :(得分:0)

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

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