在将对象序列化为JSON时,循环引用检测到异常
本文关键字:引用 循环 检测 异常 对象 序列化 JSON | 更新日期: 2023-09-27 18:17:19
正如在这篇文章中提到的,我在序列化实体框架代理时遇到了Json序列化错误:
但不同之处在于,I 在我的实体中没有有循环引用,而且它只在我们的生产环境中出现。本地一切正常…在序列化类型的对象时检测到循环引用"System.Data.Entity.DynamicProxies.PurchaseOrder_446B939192F161CDBC740067F174F7A6059B0F9C0EEE68CD3EBBD63CF9AF5BD0"。
我的实体:
public interface IEntity
{
Guid UniqueId { get; }
int Id { get; }
}
public class Entity : IEntity
{
public int Id { get; set; }
public Guid UniqueId { get; set; }
}
public class PurchaseOrder : Entity
{
public string Username { get; set; }
public string Company { get; set; }
public string SupplierId { get; set; }
public string SupplierName { get; set; }
public virtual ICollection<PurchaseOrderLine> Lines { get; set; }
}
public class PurchaseOrderLine : Entity
{
public string Code { get; set; }
public string Name { get; set; }
public decimal Quantity { get; set; }
}
在我的PurchaseOrderController上的GetCurrent操作抛出异常:
public class PurchaseOrderController : Controller
{
private readonly IUnitOfWork _unitOfWork;
public PurchaseOrderController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public JsonResult GetCurrent()
{
return Json(EnsurePurchaseOrder(), JsonRequestBehavior.AllowGet);
}
private PurchaseOrder EnsurePurchaseOrder()
{
var company = RouteData.GetRequiredString("company");
var repository = _unitOfWork.GetRepository<PurchaseOrder>();
var purchaseOrder = repository
.Include(p => p.Lines)
.FirstOrDefault
(
p => p.Company == company &&
p.Username == User.Identity.Name
);
if (purchaseOrder == null)
{
purchaseOrder = repository.Create();
purchaseOrder.UniqueId = Guid.NewGuid();
purchaseOrder.Company = company;
purchaseOrder.Username = User.Identity.Name;
_unitOfWork.SaveChanges();
}
return purchaseOrder;
}
}
选项1(推荐)
尝试在DbContext上关闭代理对象创建。
DbContext.Configuration.ProxyCreationEnabled = false;
这种情况通常是因为应用程序正在使用POCO对象(要么是T4 Generated,要么是Code-First)。当实体框架想要跟踪对象的变化时,问题就出现了,而这些变化并没有内置到POCO对象中。为了解决这个问题,EF创建了代理对象,这些对象缺少POCO对象中的属性,并且不可序列化。
我推荐这种方法的原因;使用网站意味着你可能不需要对实体框架对象进行更改跟踪(有状态),它释放了内存和cpu,因为更改跟踪是禁用的,它将以相同的方式在所有对象上一致地工作。
选项2
使用序列化器(如JSON)。asp.net已经包含在ASP中。. Net 4)允许自定义序列化对象。
我不推荐这种方法的原因是,最终将需要自定义对象序列化逻辑将代理对象序列化为其他对象类型。这意味着您需要依赖逻辑来向下游交付结果。更改对象意味着更改逻辑,而在ASP中。. Net MVC项目(任何版本),而不是仅仅改变一个视图,你有一些其他的东西要改变,是不容易知道谁写的逻辑首先。
选项3(实体框架)x +)
使用. asnotracking()来禁用特定查询上的代理对象。如果您需要使用变更跟踪,那么这是解决方案#1的一个很好的中间解决方案。
您的POCO实体是完全可序列化的。您的问题是EF运行时为您创建的动态代理通常不是。您可以将context.Configuration.ProxyCreationEnabled
设置为false
,但这样就会失去延迟加载。我强烈建议您使用Json.NET
,它支持EF实体的序列化:
ADO。. NET实体框架支持意外添加到Json。净
流行的。net高性能JSON框架
我花了无数的时间尝试各种各样的解决方案,我发现分散在整个网络,包括:
- [JsonIgnore] 内部getter
- 禁用LazyLoadingEnabled和ProxyCreationEnabled
- 设置ReferenceLoopHandling为"ignore"
- 在需要的地方谨慎使用显式加载
所有这些最终证明对我来说都是徒劳的。忽略一个属性帮助了一个查询,但伤害了另外三个查询。感觉就像在编程中玩打地鼠游戏。
我的问题的上下文是数据进出我的应用程序必须是JSON。没有别的办法。插入和更新显然造成的问题要小得多。但是,选择存储在规范化数据库中的数据(在我的例子中包括版本历史记录)进行序列化是一场噩梦。
解决方案:
将需要的数据(属性)作为匿名对象返回。
代码示例:
在这个例子中,我需要3张最近的票,基于"预定日期"。而且还需要在相关实体中存储几个属性。
var tickets =
context.TicketDetails
.Where(t => t.DateScheduled >= DateTime.Now)
.OrderBy(t => t.DateScheduled)
.Take(3)
.Include(t => t.Ticket)
.Include(t => t.Ticket.Feature)
.Include(t => t.Ticket.Feature.Property)
.AsEnumerable()
.Select(
t =>
new {
ID = t.Ticket.ID,
Address = t.Ticket.Feature.Property.Address,
Subject = t.Ticket.Subject,
DateScheduled = String.Format("{0:MMMM dd, yyyy}", t.DateScheduled)
}
);
瞧,没有自引用循环。
我意识到这种情况可能不适用于所有情况,因为实体和对象可能会发生变化。但如果其他方法都失败了,这当然是值得考虑的。
任何类都有其他类的引用,只需添加这样的属性
[Newtonsoft.Json.JsonIgnoreAttribute]
public virtual ICollection<PurchaseOrderLine> Lines { get; set; }
现在一切工作顺利
我有同样的问题,我所做的是已经传递了只需要列查看,在我的情况下。只有2 .
List<SubCategory> lstSubCategory = GetSubCateroy() // list from repo
var subCategoryToReturn = lstSubCategory.Select(S => new { Id = S.Id, Name = S.Name });
return this.Json(subCategoryToReturn , JsonRequestBehavior.AllowGet);
我有同样的错误,但是我在生产服务器和本地服务器上都看到了它。更改DbContext配置并不能完全解决我的问题。另一个不同的解决方案是
[IgnoreDataMember]
属性在DB实体引用上。如果这听起来更符合你的问题,请参阅此处的帖子。
ASP。. NET Web API序列化JSON错误:"自我引用循环"
在您的DbContext
类中,添加这行代码:
this.Configuration.ProxyCreationEnabled = false;
例如:public partial class EmpDBEntities : DbContext
{
public EmpDBEntities()
: base("name=EmpDBEntities")
{
this.Configuration.ProxyCreationEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Department> Departments { get; set; }
public virtual DbSet<Employee> Employees { get; set; }
}
循环引用的发生是因为你在对象上使用了即时加载。
你有3种方法:
- 在加载查询(linq或lambda)时关闭急切加载dbcontext . configuration . proxycreateenabled = false;
- 分离对象(=没有即时加载功能&没有代理)
- Repository.Detach (entityObject)
- DbContext.Entry (entityObject)。EntityState = EntityState。分离
- 克隆属性
- 你可以使用像AutoMapper这样的东西来克隆对象,不要使用iclonable接口,因为它也克隆对象中的ProxyProperties,所以这将不起作用。
- 如果您正在构建API,请尝试使用具有不同配置的单独项目(不返回代理)
p。代理是EF从实体框架加载时创建的对象。简而言之:这意味着它保存原始值和更新的值,以便稍后更新它们。它处理其他事情;-)
我有同样的问题,并通过取消检查Json解决了它。. NET中的项目扩展。
(见图片https://i.stack.imgur.com/RqbXZ.png)
我也不得不改变项目。
为新版本映射正确的路径<Reference Include="Newtonsoft.Json">
<HintPath>..'packages'Newtonsoft.Json.6.0.5'lib'net45'Newtonsoft.Json.dll</HintPath>
</Reference>
仍然需要配置web.config
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
注意在web中。虽然安装的版本是6.0.5,但我被迫引用旧版本(6.0.0.0)。
希望有帮助!