检测到实体框架自引用循环
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了检测到实体框架自引用循环相关的知识,希望对你有一定的参考价值。
我有一个奇怪的错误。我正在试验.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?这并没有多大意义。如果我在我的数据库中进行循环引用,我希望这会发生,但这是一个非常简单的例子。怎么可能出错?
那么基于Json.net的默认Json格式化器的正确答案是将ReferenceLoopHandling
设置为Ignore
。
只需将其添加到Global.asax中的Application_Start
:
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter
.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
这是正确的方法。它将忽略指向对象的引用。
其他响应的重点是通过排除数据或通过制作Facade对象来更改返回的列表,有时这不是一个选项。
使用JsonIgnore
属性来限制引用可能非常耗时,并且如果要从另一个点开始序列化树将是一个问题。
以自引用为例
=============================================================
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);
如果您尝试在Blazor(ASP.NET Core Hosted)模板中更改此设置,则需要将以下内容传递给AddNewtonsoftJson
项目中Startup.cs
中的Server
调用:
services.AddMvc().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore
);
我在.net核心网站上遇到了同样的问题。接受的答案对我不起作用,但我发现ReferenceLoopHandling.Ignore和PreserveReferencesHandling.Objects的组合修复了它。
//serialize item
var serializedItem = JsonConvert.SerializeObject(data, Formatting.Indented,
new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
发生这种情况是因为您正在尝试直接序列化EF对象集合。由于部门与员工和员工之间存在关联,因此JSON序列化程序将循环读取d.Employee.Departments.Employee.Departments等...
要在序列化之前修复此问题,请创建一个包含所需道具的匿名类型
例子(伪代码:
departments.select(dep => new {
dep.Id,
Employee = new {
dep.Employee.Id, dep.Employee.Name
}
});
我有同样的问题,发现你只需将[JsonIgnore]
属性应用于你不想序列化的导航属性。它仍将序列化父实体和子实体,但只是避免了自引用循环。
我知道这个问题很老了,但它仍然很受欢迎,我看不到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
异常在我的情况下。
主要问题是序列化与其他实体模型(外键关系)有关系的实体模型。这种关系导致自引用,这将在序列化为json或xml时抛出异常。有很多选择。不使用自定义模型序列化实体模型。使用Automapper或Valueinjector映射到自定义模型(对象映射)的实体模型数据中的值或数据然后返回请求,它将序列化而没有任何其他问题。或者您可以序列化实体模型,因此首先在实体模型中禁用代理
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);
消息错误意味着您有一个自引用循环。
您生成的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;
}
}
在上下文模型分部类定义的构造函数中添加一行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)
{
}
}
我只有一个我想要使用的模型,所以我最终得到了以下代码:
var JsonImageModel = Newtonsoft.Json.JsonConvert.SerializeObject(Images, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
我也可能会考虑为每个控制器/操作添加显式示例,这里也包含:
即config.SetActualResponseType(typeof(SomeType),“Values”,“Get”);
以上是关于检测到实体框架自引用循环的主要内容,如果未能解决你的问题,请参考以下文章