EF 4.1 – Code First – JSON循环引用序列化错误

我得到了一个循环引用序列化错误,虽然,据我所知,我没有任何循环引用。 我从数据库中检索一组订单,并以JSON的forms将它们发送到客户端。 所有的代码如下所示。

这是错误的:

错误

序列化“System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812”types的对象时检测到循环引用。 说明:执行当前Web请求期间发生未处理的exception。 请查看堆栈跟踪,了解有关错误的更多信息以及源代码的来源。

exception详细信息:System.InvalidOperationException:在序列化“System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812”types的对象时检测到循环引用。

源错误:

在执行当前Web请求期间生成未处理的exception。 有关exception的来源和位置的信息可以使用下面的exception堆栈跟踪来标识。

我的课程如下:

订购

public class Order { [Key] public int OrderId { get; set; } public int PatientId { get; set; } public virtual Patient Patient { get; set; } public int CertificationPeriodId { get; set; } public virtual CertificationPeriod CertificationPeriod { get; set; } public int AgencyId { get; set; } public virtual Agency Agency { get; set; } public int PrimaryDiagnosisId { get; set; } public virtual Diagnosis PrimaryDiagnosis { get; set; } public int ApprovalStatusId { get; set; } public virtual OrderApprovalStatus ApprovalStatus { get; set; } public int ApproverId { get; set; } public virtual User Approver { get; set; } public int SubmitterId { get; set; } public virtual User Submitter { get; set; } public DateTime ApprovalDate { get; set; } public DateTime SubmittedDate { get; set; } public Boolean IsDeprecated { get; set; } } 

患者

 public class Patient { [Key] public int PatientId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string MiddleInitial { get; set; } public bool IsMale; public DateTime DateOfBirth { get; set; } public int PatientAddressId { get; set; } public Address PatientAddress { get; set; } public bool IsDeprecated { get; set; } } 

authentication期限

 public class CertificationPeriod { [Key] public int CertificationPeriodId { get; set; } public DateTime startDate { get; set; } public DateTime endDate { get; set; } public bool isDeprecated { get; set; } } 

机构

 public class Agency { [Key] public int AgencyId { get; set; } public string Name { get; set; } public int PatientAddressId { get; set; } public virtual Address Address { get; set; } } 

诊断

 public class Diagnosis { [Key] public int DiagnosisId { get; set; } public string Icd9Code { get; set; } public string Description { get; set; } public DateTime DateOfDiagnosis { get; set; } public string Onset { get; set; } public string Details { get; set; } } 

OrderApprovalStatus

 public class OrderApprovalStatus { [Key] public int OrderApprovalStatusId { get; set; } public string Status { get; set; } } 

用户

 public class User { [Key] public int UserId { get; set; } public string Login { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string NPI { get; set; } public string Email { get; set; } } 

注:地址类是编辑期间的新增加

地址

 public class Address { [Key] public int AddressId { get; set; } public string StreetAddress { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } public string Phone { get; set; } public string Title { get; set; } public string Label { get; set; } } 

执行序列化的代码在这里:

摘自OrderController

  public ActionResult GetAll() { return Json(ppEFContext.Orders, JsonRequestBehavior.AllowGet); } 

谢谢

您可以尝试从所有导航属性中删除virtual关键字,以禁用延迟加载和代理创build,然后使用意外加载来显式加载所需的对象图:

 public ActionResult GetAll() { return Json(ppEFContext.Orders .Include(o => o.Patient) .Include(o => o.Patient.PatientAddress) .Include(o => o.CertificationPeriod) .Include(o => o.Agency) .Include(o => o.Agency.Address) .Include(o => o.PrimaryDiagnosis) .Include(o => o.ApprovalStatus) .Include(o => o.Approver) .Include(o => o.Submitter), JsonRequestBehavior.AllowGet); } 

参考你以前的post ,看起来你的应用程序并不依赖懒加载,因为你在那里引入了虚拟属性来懒惰地加载对象图,可能会导致现在的序列化问题。

编辑

没有必要从导航属性中删除virtual关键字(这将使模型的延迟加载完全不可能)。 这足以禁用代理创build(也会禁用延迟加载),以便代理受到干扰的特定情况下,如序列化:

 ppEFContext.Configuration.ProxyCreationEnabled = false; 

这将仅禁用特定上下文实例ppEFContext代理创build。

(我刚刚看过,@WillC已经在这里提到过了,请给他回答。

当你知道你需要从特定的上下文进行序列化时,你可以像下面那样禁用特定查询的代理创build。 这对我来说,并比修改我的模型类更好。

 using (var context = new MeContext()) { context.Configuration.ProxyCreationEnabled = false; return context.cars.Where(w => w.Brand == "Ferrari") } 

这种方法取消了上下文的特定实例的代理对象types,所以返回的对象是实际的类,因此序列化不成问题。

即:

 {Models.car} 

代替

 {System.Data.Entity.DynamicProxies.car_231710A36F27E54BC6CE99BB50E0FE3B6BD4462EC‌​A19695CD1BABB79605296EB} 

问题是你实际上是序列化一个entity framework生成的代理对象。 不幸的是,这与使用JSON序列化程序时有一些问题。 为了JSON兼容性,您可能会考虑将您的实体映射到特殊的简单POCO类。

有一个要添加到entity framework对象的属性

 [ScriptIgnore] 

这使得代码不能执行循环引用。

我认为他们已经在最新版本中解决了这个问题。

请参阅“ 序列化和反序列化JSON – >序列化和保留对象引用 ”一节下的帮助文档 。

初始化JSON.Net序列化程序时设置此设置:

 PreserveReferencesHandling = PreserveReferencesHandling.Objects; 

所以一个例子是这样的:

 var serializerSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects }; string json = JsonConvert.SerializeObject(people, Formatting.Indented, serializerSettings); 

我证实,这与我的代码的第一个解决scheme,并在导航属性中的循环引用。 如果你看看产生的JSON,它应该有“$ id”和“$ ref”属性。

另一种解决scheme是使用匿名types作为LINQ查询的结果。

在我的项目中,我广泛使用了延迟加载,禁用它并不是正确的做法。

另一种解决scheme是,如果只需要对象中的某些值,则构build一个匿名类并将其返回,如下例所示:

 public JsonResult AjaxFindByName(string term) { var customers = context.Customers .Where(c => c.Name.ToUpper().Contains(term.ToUpper())).Take(10) .AsEnumerable() .Select(c => new { value = c.Name, SSN = String.Format(@"{0:000\-00\-0000}", c.SSN), CustomerID = c.CustomerID }); return Json(customers, JsonRequestBehavior.AllowGet); } 

循环引用发生是因为您使用对象上的急切加载。

你有几个方法:

  • 当您加载您的查询(linq或lambda)时closures加载加载DbContext.Configuration.ProxyCreationEnabled = false;
  • 从Domainmodel中删除虚拟关键字
  • 分离对象(=没有渴望的加载function和没有代理)
    • Repository.Detach(entityObject)
    • DbContext.Entry(entityObject).EntityState = EntityState.Detached
  • 克隆属性
    • 你可以使用像AutoMapper这样的东西来克隆对象,不要使用ICloneable接口,因为它也克隆了对象中的ProxyProperties,所以这是行不通的。
  • 如果您正在构buildAPI,请尝试使用不同configuration的separte项目(不返回代理)

PS。 代理是EF从entity framework加载它时创build的对象。 简而言之:这意味着它保存了原始值和更新值,以便稍后可以更新。 它处理其他的事情;-)

对于那些使用代理EF / Linq2SQL类我的解决scheme是简单地删除我的子实体的父引用。

所以在我的模型中,我select了关系,并将Parent引用改为Internal,而不是Public。

可能不是一个理想的解决scheme,但为我工作。

您可以删除virtual关键字:

public virtual Patient Patient { get; set; } public virtual Patient Patient { get; set; } – > public Patient Patient { get; set; } public Patient Patient { get; set; }

请记住,当你删除虚拟关键字,延迟加载将被closures。