检测到实体框架自引用循环[重复]

Posted

技术标签:

【中文标题】检测到实体框架自引用循环[重复]【英文标题】:Entity framework self referencing loop detected [duplicate] 【发布时间】:2013-10-28 08:39:00 【问题描述】:

我有一个奇怪的错误。我正在尝试使用 .NET 4.5 Web API、Entity Framework 和 MS SQL Server。我已经创建了数据库并设置了正确的主键和外键以及关系。

我创建了一个 .edmx 模型并导入了两个表:Employee 和 Department。一个部门可以有很多员工,并且存在这种关系。我使用脚手架选项创建了一个名为 EmployeeController 的新控制器,以使用实体框架创建具有读/写操作的 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'。路径 '[0].Department.Employees'.","ExceptionType":"Newtonsoft.Json.JsonSerializationException","StackTrace":" ...

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

【问题讨论】:

【参考方案1】:

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

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

HttpConfiguration config = GlobalConfiguration.Configuration;

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

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

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

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

【讨论】:

var config = GlobalConfiguration.Configuration.Formatters.JsonFormatter? HttpConfiguration 配置 = GlobalConfiguration.Configuration; //System.Web.Http 无效果,仍然出现异常 Doug Beard,您能告诉我有关您的课程的任何信息吗? 我得到一个自引用循环错误,即使我在Application_Start 中有上述代码 并且 在相关属性上有[JsonIgnore]...我也有@ 987654328@ 在这些属性上,并已将 ProxyCreationEnabled 设置为 false 用于实体框架...【参考方案2】:

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

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

示例(伪)代码:

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

【讨论】:

在这种情况下,我将使用模型类,例如部门.select(dep => new dep.Id, Employee = new ModelClass Id = dep.Employee.Id, Name = dep.Employee .Name ); @AtulChaudhary 这取决于你想要什么。如果您只是想通过 SPA 中的 AJAX 使用服务,那么这就足够了,如果您使用 MVVM,我会说更好,因为它允许您在服务器上指定 ViewModel。如果您想将此服务用作 N 层 API 的一部分,由其他 .net 服务使用,那么另一个答案更合适,因为它保持数据库合同完好无损(如,您仍将发送一个合格的EF 实体)。【参考方案3】:

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

【讨论】:

【参考方案4】:

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

我是ASP.net Core,需要在Startup.cs文件中添加新的JsonOutputFormatter

    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 异常。

【讨论】:

【参考方案5】:

主要问题是序列化与其他实体模型有关系的实体模型(外键关系)。这种关系会导致自引用,这将在序列化为 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 dfsdget;set;
   [DataMember]
   string dfsdget;set;
   //exclude  the relation without giving datamember tag
   List<Department> Departmentsget;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);

【讨论】:

选项 1 使用 DataContract 和 DataMember 属性运行良好,但是在结果末尾我得到“Employees”:[“$ref”:“1”]]。它已经消除了症状,但问题仍然存在。当然,其他所有人都会遇到完全相同的问题。我是否错误地接近了这个解决方案? 我在 global.asax 文件中设置了 ProxyCreationEnabled = false 和 jason thingy。它就像一个他妈的魅力。谢谢【参考方案6】:

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

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

【讨论】:

【参考方案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)
    
    

【讨论】:

这在 2017 年对我有用,其他解决方案不起作用【参考方案8】:

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

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

【讨论】:

【参考方案9】:

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

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

【讨论】:

【参考方案10】:

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

//serialize item
var serializedItem = JsonConvert.SerializeObject(data, Formatting.Indented, 
new JsonSerializerSettings

     PreserveReferencesHandling = PreserveReferencesHandling.Objects,
     ReferenceLoopHandling = ReferenceLoopHandling.Ignore
);

【讨论】:

当我有一个 1 对 1 的关系时,依赖实体在模型中没有外键,只有父对象,使用 ReferenceLoopHandling.Ignore 在序列化时也会忽略子对象内部的父对象.【参考方案11】:

我也可能会考虑为每个控制器/动作添加显式示例,这里也有介绍:

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

【讨论】:

【参考方案12】:

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

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
            ;
        

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

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

【讨论】:

【参考方案13】:

以自引用为例

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

【讨论】:

以上是关于检测到实体框架自引用循环[重复]的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.NET Core 中检测到自引用循环 [重复]

检测到自引用循环 - 从 WebApi 取回数据到浏览器

如何在不违反主键约束的情况下插入具有循环引用的实体框架

如何删除实体框架中的循环引用?

如何在实体框架中包含排序的导航属性[重复]

实体框架代码第一个值对象持久保存到数据库[重复]