序列化“SubSonic.Schema.DatabaseColumn”类型的对象时检测到循环引用。

Posted

技术标签:

【中文标题】序列化“SubSonic.Schema.DatabaseColumn”类型的对象时检测到循环引用。【英文标题】:A circular reference was detected while serializing an object of type 'SubSonic.Schema .DatabaseColumn'. 【发布时间】:2010-11-12 07:24:31 【问题描述】:

我正在尝试做一个简单的 JSON 返回,但我遇到了以下问题。

public JsonResult GetEventData()

    var data = Event.Find(x => x.ID != 0);
    return Json(data);

我得到了一个 HTTP 500 异常,如本问题的标题所示。我也试过了

var data = Event.All().ToList()

同样的问题。

这是一个错误还是我的实现?

【问题讨论】:

看看这个。有一个使用ScriptIgnore 属性的解决方案。 ***.com/questions/1193857/subsonic-3-0-0-2-structs-tt 这对我来说是最好的解决方案;我有 Game > Tournament > Game > Tournament > Game 等。我在 Tournament.Game 属性上放置了一个 ScriptIgnore 属性,它运行良好:) 如果有人想要“自动化”(非最佳实践)解决方案,无需额外代码,请查看此 QA:Do not serialize Entity Framework class references in JSON (ServiceStack.Text library) 【参考方案1】:

您的对象层次结构中似乎存在 JSON 序列化程序不支持的循环引用。你需要所有的列吗?您可以只在视图中选择您需要的属性:

return Json(new 
  
    PropertyINeed1 = data.PropertyINeed1,
    PropertyINeed2 = data.PropertyINeed2
);

这将使您的 JSON 对象更轻巧且更易于理解。如果你有很多属性,AutoMapper 可以用来在 DTO 对象和 View 对象之间映射automatically。

【讨论】:

我想也许选择我想要的可能会起作用我认为循环引用是因为在事件中我有 IQueryable 反过来又会有一个 IQueryable Automapper 不能保证您不会遇到此问题。我来这里是为了寻找答案,我实际上正在使用 automapper。 请看@ClayKaboom 的答案,因为它解释了为什么它可能是圆形的【参考方案2】:

我遇到了同样的问题,被using Newtonsoft.Json;解决了

var list = JsonConvert.SerializeObject(model,
    Formatting.None,
    new JsonSerializerSettings() 
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);

return Content(list, "application/json");

【讨论】:

这个内联代码对我来说很好用。 kravits88 提到的全局配置中的相同内容对我不起作用。此外,应该更新方法签名以返回此代码的 ContentResult。 这应该被标记为最佳答案,因为它涵盖了您不能花费数小时将您的对象转换为其他表示的情况,如标记为接受的答案。 我这样做了,但现在我在序列化过程中得到一个空引用异常,不知道是什么属性导致它。【参考方案3】:

这实际上是因为复杂的对象导致生成的 json 对象失败。 它失败了,因为当对象被映射时,它映射了孩子,孩子映射了他们的父母,使循环引用发生。 Json 将花费无限时间来序列化它,因此它可以防止异常问题。

实体框架映射也会产生相同的行为,解决方案是丢弃所有不需要的属性。

只要明确最终答案,整个代码就是:

public JsonResult getJson()

    DataContext db = new DataContext ();

    return this.Json(
           new 
                Result = (from obj in db.Things select new Id = obj.Id, Name = obj.Name)
               
           , JsonRequestBehavior.AllowGet
           );

如果您不希望 Result 属性内的对象,也可能是以下情况:

public JsonResult getJson()

    DataContext db = new DataContext ();

    return this.Json(
           (from obj in db.Things select new Id = obj.Id, Name = obj.Name)
           , JsonRequestBehavior.AllowGet
           );

【讨论】:

+1 清晰易懂,感谢@Clay。我喜欢你对错误背后概念的解释。【参考方案4】:

总结一下,有4个解决方案:

解决方案一:关闭DBContext的ProxyCreation,最后恢复。

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    
        bool proxyCreation = db.Configuration.ProxyCreationEnabled;
        try
        
            //set ProxyCreation to false
            db.Configuration.ProxyCreationEnabled = false;

            var data = db.Products.ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        
        catch (Exception ex)
        
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        
        finally
        
            //restore ProxyCreation to its original state
            db.Configuration.ProxyCreationEnabled = proxyCreation;
        
    

解决方案 2:通过设置 ReferenceLoopHandling 来使用 JsonConvert 以忽略序列化程序设置。

    //using using Newtonsoft.Json;

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    
        try
        
            var data = db.Products.ToList();

            JsonSerializerSettings jss = new JsonSerializerSettings  ReferenceLoopHandling = ReferenceLoopHandling.Ignore ;
            var result = JsonConvert.SerializeObject(data, Formatting.Indented, jss);

            return Json(result, JsonRequestBehavior.AllowGet);
        
        catch (Exception ex)
        
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        
    

以下两种解决方案是相同的,但使用模型更好,因为它是强类型的。

解决方案 3:返回一个仅包含所需属性的模型。

    private DBEntities db = new DBEntities();//dbcontext

    public class ProductModel
    
        public int Product_ID  get; set;

        public string Product_Name  get; set;

        public double Product_Price  get; set;
    

    public ActionResult Index()
    
        try
        
            var data = db.Products.Select(p => new ProductModel
                                                
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                ).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        
        catch (Exception ex)
        
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        
    

解决方案 4:返回一个仅包含所需属性的新动态对象。

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    
        try
        
            var data = db.Products.Select(p => new
                                                
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                ).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        
        catch (Exception ex)
        
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        
    

【讨论】:

【参考方案5】:

JSON 与 xml 和其他各种格式一样,是一种基于树的序列化格式。如果您的对象中有循环引用,它不会爱您,因为“树”将是:

root B => child A => parent B => child A => parent B => ...

通常有一些方法可以禁用沿特定路径的导航;例如,使用XmlSerializer,您可以将父属性标记为XmlIgnore。我不知道有问题的 json 序列化程序是否可以做到这一点,也不知道 DatabaseColumn 是否有合适的标记(非常不太可能,因为它需要引用每个序列化 API)

【讨论】:

【参考方案6】:

[JsonIgnore] 添加到模型中的虚拟属性中。

【讨论】:

【参考方案7】:

使用 Newtonsoft.Json:在您的 Global.asax Application_Start 方法中添加以下行:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

【讨论】:

显然看起来很直接,但对我不起作用【参考方案8】:

这是因为用于生成 EntityFramework 实体的新 DbContext T4 模板。为了能够执行更改跟踪,此模板使用代理模式,通过用它们包装您的漂亮 POCO。这会导致使用 javascriptSerializer 进行序列化时出现问题。

那么两个解决方案是:

    要么你只是序列化并返回你在客户端上需要的属性

    您可以通过在上下文的配置中设置代理来关闭代理的自动生成

    context.Configuration.ProxyCreationEnabled = false;

在下面的文章中有很好的解释。

http://juristr.com/blog/2011/08/javascriptserializer-circular-reference/

【讨论】:

【参考方案9】:

提供的答案很好,但我认为可以通过添加“架构”视角来改进它们。

调查

MVC's Controller.Json 函数正在完成这项工作,但在这种情况下它在提供相关错误方面非常糟糕。通过使用Newtonsoft.Json.JsonConvert.SerializeObject,错误准确地指定了触发循环引用的属性是什么。这在序列化更复杂的对象层次结构时特别有用。

适当的架构

永远不要尝试序列化数据模型(例如 EF 模型),因为 ORM 的导航属性在序列化方面是通向毁灭的道路。数据流应如下:

Database -> data models -> service models -> JSON string 

可以使用自动映射器从数据模型中获取服务模型(例如Automapper)。虽然这并不能保证没有循环引用,但正确的设计应该做到这一点:服务模型应该包含服务使用者需要的内容(即属性)。

在极少数情况下,当客户端请求涉及不同级别的相同对象类型的层次结构时,服务可以创建具有父->子关系的线性结构(仅使用标识符,而不是引用)。

现代应用程序倾向于避免一次加载复杂的数据结构,并且服务模型应该很精简。例如:

    访问事件 - 仅加载标头数据(标识符、名称、日期等)-> 仅包含标头数据的服务模型 (JSON) 受管理的与会者列表 - 访问弹出窗口并延迟加载列表 -> 仅包含与会者列表的服务模型 (JSON)

【讨论】:

【参考方案10】:

避免直接转换表格对象。如果在其他表之间设置了关系,则可能会引发此错误。 相反,您可以创建一个模型类,为类对象赋值,然后对其进行序列化。

【讨论】:

【参考方案11】:

我正在使用修复,因为在 MVC5 视图中使用了 Knockout。

动作

return Json(ModelHelper.GetJsonModel<Core_User>(viewModel));

功能

   public static TEntity GetJsonModel<TEntity>(TEntity Entity) where TEntity : class
    
        TEntity Entity_ = Activator.CreateInstance(typeof(TEntity)) as TEntity;
        foreach (var item in Entity.GetType().GetProperties())
        
            if (item.PropertyType.ToString().IndexOf("Generic.ICollection") == -1 && item.PropertyType.ToString().IndexOf("SaymenCore.DAL.") == -1)
                item.SetValue(Entity_, Entity.GetPropValue(item.Name));
        
        return Entity_;  
    

【讨论】:

【参考方案12】:

您可以注意到导致循环引用的属性。然后您可以执行以下操作:

private Object DeCircular(Object object)

   // Set properties that cause the circular reference to null

   return object

【讨论】:

【参考方案13】:
//first: Create a class as your view model

public class EventViewModel 

 public int Idget;set
 public string Property1get;set;
 public string Property2get;set;

//then from your method
[HttpGet]
public async Task<ActionResult> GetEvent()

 var events = await db.Event.Find(x => x.ID != 0);
 List<EventViewModel> model = events.Select(event => new EventViewModel()
 Id = event.Id,
 Property1 = event.Property1,
 Property1 = event.Property2
).ToList();
 return Json(new data = model , JsonRequestBehavior.AllowGet);

【讨论】:

这不能回答问题【参考方案14】:

解决这个问题的一个更简单的替代方法是返回一个字符串,并使用 JavaScriptSerializer 将该字符串格式化为 json。

public string GetEntityInJson()

   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new  ID = x.ID, AnotherAttribute = x.AnotherAttribute );
   return j.Serialize(entityList );

“选择”部分很重要,它在视图中选择您想要的属性。某些对象具有父对象的引用。如果不选择属性,如果只是将表作为一个整体,可能会出现循环引用。

不要这样做:

public string GetEntityInJson()

   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.toList();
   return j.Serialize(entityList );

如果您不想要整个表格,请改为这样做:

public string GetEntityInJson()

   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new  ID = x.ID, AnotherAttribute = x.AnotherAttribute );
   return j.Serialize(entityList );

这有助于用更少的数据渲染视图,只使用您需要的属性,并使您的网络运行得更快。

【讨论】:

以上是关于序列化“SubSonic.Schema.DatabaseColumn”类型的对象时检测到循环引用。的主要内容,如果未能解决你的问题,请参考以下文章

什么是序列化? 如何实现(反)序列化 序列化的应用

什么是序列化? 如何实现(反)序列化 序列化的应用

如何序列化/反序列化的ArrayList

什么是java的序列化和反序列化?

Netty_05_六种序列化方式(JavaIO序列化 XML序列化 Hessian序列化 JSON序列化 Protobuf序列化 AVRO序列化)(实践类)

Netty_05_六种序列化方式(JavaIO序列化 XML序列化 Hessian序列化 JSON序列化 Protobuf序列化 AVRO序列化)(实践类)