循环引用在将对象序列化为JSON时检测到异常

时间:2013-06-05 20:44:18

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

正如this帖子中提到的,我在序列化实体框架代理时遇到了Json序列化错误:

  

在序列化类型的对象时检测到循环引用   '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;
    }
}

9 个答案:

答案 0 :(得分:42)

选项1(推荐)

Try turning off Proxy object creation on your DbContext

DbContext.Configuration.ProxyCreationEnabled = false;

通常这种情况是因为应用程序正在使用POCO对象(T4生成或代码优先)。当Entity Framework想要跟踪对象中没有内置到POCO对象中的更改时,就会出现问题。要解决此问题,EF会创建缺少POCO对象中的属性的代理对象,并且不可序列化。

我推荐这种方法的原因;使用网站意味着您可能不需要对Entity Framework对象进行更改跟踪(有状态),它可以释放内存和cpu,因为更改跟踪已禁用,并且它将以相同的方式在所有对象上一致地工作。

选项2

使用序列化程序(如ASP.Net 4中已包含的JSON.Net),允许自定义序列化对象。

我不推荐这种方法的原因是最终自定义对象序列化逻辑需要将代理对象串行化为其他对象类型。这意味着您依赖逻辑来向下游传递结果。更改对象意味着更改逻辑,并且在ASP.Net MVC项目(任何版本)中,而不是仅更改视图,您还有其他一些需要改变的东西,除了先编写逻辑的人之外,这是不容易知道的。

选项3(实体框架5.x +)

使用.AsNoTracking()将禁用特定查询的代理对象。如果您需要使用更改跟踪,这可以为解决方案#1提供一个很好的中间解决方案。

答案 1 :(得分:30)

您的POCO实体是完全可序列化的。您的问题是EF运行时为您创建的动态代理通常不会。您可以将context.Configuration.ProxyCreationEnabled设置为false,但之后会丢失延迟加载。我强烈建议您使用支持EF实体序列化的Json.NET

ADO.NET Entity Framework support accidently added to Json.NET

Popular high-performance JSON framework for .NET

答案 2 :(得分:12)

我花了无数个小时尝试了我在网络上散布的所有各种解决方案,包括:

  • [JsonIgnore]
  • 内部吸气剂
  • 禁用LazyLoadingEnabled和ProxyCreationEnabled
  • 将ReferenceLoopHandling设置为&#34;忽略&#34;
  • 在需要的地方小心使用显式加载

所有这些对我来说最终都没有结果。忽略一个属性有助于一个查询,但伤害了另外三个。感觉就像编程相当于whack-a-mole。

我的问题的背景是我的应用程序中的数据必须是JSON。没办法解决它。插入和更新显然不会造成问题。但选择存储在规范化数据库中的数据(在我的情况下包括版本历史)要被序列化是一场噩梦。

解决方案:

  

将您需要的数据(属性)作为匿名对象返回。

代码示例:

  

在这种情况下,我需要3张最新的门票,基于&#34; Date Scheduled&#34;。但是还需要存储在相关实体中的几个属性。

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

瞧,没有自我引用循环。

我意识到,鉴于实体和对象可能会发生变化,这种情况可能并不适用于所有情况。但如果一切都失败了,那肯定值得考虑一下。

答案 3 :(得分:2)

无论哪个类都有其他类的引用,只需添加像这样的属性

[Newtonsoft.Json.JsonIgnoreAttribute]
public virtual ICollection<PurchaseOrderLine> Lines { get; set; }

现在一切顺利

答案 4 :(得分:1)

我有同样的错误,但是我在生产服务器和本地都看到了它。更改DbContext配置并没有完全解决我的问题。

给了我一个不同的解决方案
[IgnoreDataMember]
数据库实体引用的

属性。如果这听起来与您的问题更相关,请参阅此处的帖子。

ASP.NET Web API Serialized JSON Error: "Self Referencing loop"

答案 5 :(得分:1)

我遇到了同样的问题,我所做的只是通过了需要的列来查看,在我的情况下。只有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);

答案 6 :(得分:1)

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

答案 7 :(得分:0)

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

您有3种方法:

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

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

答案 8 :(得分:0)

我遇到了同样的问题并通过在参考管理器的项目Extensions中取消选中Json.NET来解决它。

(见图片http://i.stack.imgur.com/RqbXZ.png

我还必须更改project.csproj文件以映射新版本的正确路径:

<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.config文件中,虽然安装的版本是6.0.5,但我被迫引用了OLDER(6.0.0.0)版本。

希望它有所帮助!