EF 4.1 - 代码优先 - JSON循环引用序列化错误

时间:2011-04-07 21:56:38

标签: c# json entity-framework serialization asp.net-mvc-3

我收到循环参考序列化错误,但据我所知,我没有任何循环引用。我正在从数据库中检索一组订单并将它们作为JSON发送到客户端。所有代码如下所示。

这是错误:

  

错误

     

检测到循环参考   在序列化类型的对象时   'System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812'。   描述:未处理的异常   在执行期间发生   当前的网络请求。请查看   堆栈跟踪以获取更多信息   错误及其来源   代码。

     

异常详细信息:   System.InvalidOperationException:A   检测到循环引用   序列化类型的对象   'System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812'。

     

来源错误:

     

生成了未处理的异常   在执行当前   网络请求。有关的信息   异常的起源和位置   可以使用例外来识别   堆栈跟踪下面。

我的课程如下:

顺序

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

认证期

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

由于

11 个答案:

答案 0 :(得分:48)

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

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

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

修改

没有必要从导航属性中删除virtual关键字(这会使模型完全无法进行延迟加载)。它足以禁用代理创建(也禁用延迟加载),用于代理令人不安的特定情况,如序列化:

ppEFContext.Configuration.ProxyCreationEnabled = false;

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

(我刚刚看到,@ WillC已经在这里提到过了。请为此编辑提供支持请回答他。)

答案 1 :(得分:41)

如果您知道需要从特定上下文序列化,则可以禁用该特定查询的代理创建,如下所示。这对我有用,并且比修改我的模型类更好。

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

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

即:

{Models.car} 

而不是

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

答案 2 :(得分:9)

问题在于您实际上正在序列化实体框架生成的代理对象。不幸的是,当与JSON序列化程序一起使用时,这会出现一些问题。为了JSON兼容性,您可以考虑将实体映射到特殊的简单POCO类。

答案 3 :(得分:8)

有一个要添加到Entity Framework对象的属性

[ScriptIgnore]

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

答案 4 :(得分:7)

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

查看“序列化和反序列化JSON - >序列化和保留对象引用”部分下的help docs

初始化JSON.Net Serializer时设置此设置:

PreserveReferencesHandling = PreserveReferencesHandling.Objects;

一个例子是:

var serializerSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects };

string json = JsonConvert.SerializeObject(people, Formatting.Indented, serializerSettings);

我确认这适用于我的代码第一个解决方案,以及导航属性中的循环引用。如果你看看生成的JSON,它应该在任何地方都有“$ id”和“$ ref”属性。

答案 5 :(得分:6)

另一种解决方案是使用匿名类型作为LINQ查询的结果。

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

答案 6 :(得分:3)

另一种解决方案,如果只需要来自对象的某些值,则构建一个匿名类并返回它,如下例所示:

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

答案 7 :(得分:2)

循环引用的发生是因为您在对象上使用了预先加载。

您有几种方法:

  • 加载查询(linq或lambda)时关闭急切加载    DbContext.Configuration.ProxyCreationEnabled = false;
  • 从Domainmodel中删除虚拟关键字
  • 分离对象(=没有急切的加载功能和没有代理)
    • Repository.Detach(entityObject)
    • DbContext.Entry(entityObject).EntityState = EntityState.Detached
  • 克隆属性
    • 您可以使用类似AutoMapper的东西来克隆对象,不要使用ICloneable接口,因为它还会克隆对象中的ProxyProperties,因此无法使用。
  • 如果您正在构建API,请尝试使用具有不同配置的separte项目(不返回代理)

PS。代理是EF从实体框架加载时创建的对象。简而言之:它意味着它保存原始值和更新值,以便以后更新。它处理其他事情; - )

答案 8 :(得分:0)

对于使用代理EF / Linq2SQL类的人,我的解决方案是简单地删除子实体上的父引用。

所以在我的模型中,我选择了关系并将父引用更改为内部而不是公共。

可能不是一个理想的解决方案,但对我有用。

答案 9 :(得分:0)

您可以删除virtual关键字:

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

请注意,删除虚拟关键字时,将关闭延迟加载。

答案 10 :(得分:0)