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

问题描述 投票:92回答:12

我有一个奇怪的错误。我正在试验.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?这并没有多大意义。如果我在我的数据库中进行循环引用,我希望这会发生,但这是一个非常简单的例子。怎么可能出错?

c# serialization entity-framework-4 asp.net-web-api
12个回答
146
投票

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

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

HttpConfiguration config = GlobalConfiguration.Configuration;

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

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

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

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


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

0
投票

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

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

0
投票

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

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

50
投票

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

要在序列化之前修复此问题,请创建一个包含所需道具的匿名类型

例子(伪代码:

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

32
投票

我有同样的问题,发现你只需将[JsonIgnore]属性应用于你不想序列化的导航属性。它仍将序列化父实体和子实体,但只是避免了自引用循环。


16
投票

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

我是ASP.net Core的情况,你需要在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异常在我的情况下。


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

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

8
投票

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

您生成的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;
    }
}

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

3
投票

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.