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


170

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

public JsonResult GetEventData()
{
    var data = Event.Find(x => x.ID != 0);
    return Json(data);
}

我得到一个HTTP 500,但此问题的标题中显示了例外。我也试过

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

那也带来了同样的问题。

这是错误还是我的实现?


1
看这个 有一个使用该ScriptIgnore属性的解决方案。stackoverflow.com/questions/1193857/subsonic-3-0-0-2-structs-tt
freddoo

这对我来说是最好的解决方案。我有游戏>锦标赛>游戏>锦标赛>游戏,等等。我ScriptIgnore在Tournament.Game属性上放置了一个属性,它运行良好:)
eth0

如果有人希望此问题不需要任何额外代码的“自动化”(不是最佳实践)解决方案,请查看此质量检查:请勿序列化JSON(ServiceStack.Text库)中的Entity Framework类引用
nikib3ro,2013年

Answers:


175

看来您的对象层次结构中有循环引用,而JSON序列化程序不支持。您是否需要所有列?您只能在视图中选择所需的属性:

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

这将使您的JSON对象更轻便,更易于理解。如果您有许多属性,则可以使用AutoMapper 自动在DTO对象和View对象之间进行映射。


我认为也许选择我想要的可能可行,我认为循环引用是因为在事件中我有IQueryable <Category>,而后者又将有一个IQueryable <Event>
Jon

7
Automapper不能保证您不会遇到此问题。我来这里寻找答案,实际上是在使用自动映射器。
肯帕奇船长

1
请务必查看@ClayKaboom的答案,因为它解释了为什么它可能是圆形的
PandaWood 2014年

106

我有同样的问题,并通过解决 using Newtonsoft.Json;

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

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

3
这个内联代码对我来说很好用。kravits88提到的全局配置中的相同内容对我不起作用。另外,应该更新方法签名以返回此代码的ContentResult。
BiLaL 2015年

6
应该将其标记为最佳答案,因为它涵盖了您无法花费数小时将对象转换为其他表示形式(如标记为已接受的答案)的情况。
雷南

56

这实际上是因为复杂对象导致了生成的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
+1清楚易懂,谢谢@Clay。我喜欢您对错误背后的概念的解释。
Ajay2707 '17

14

总结起来,有4种解决方案:

解决方案1:为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);
        }
    }

7

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

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

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


4

这是因为新的DbContext T4模板用于生成EntityFramework实体。为了能够执行更改跟踪,此模板通过将漂亮的POCO与它们包装在一起,从而使用了Proxy模式。然后,在使用JavaScriptSerializer进行序列化时会导致问题。

因此,这两种解决方案是:

  1. 您只需序列化并在客户端上返回所需的属性
  2. 您可以通过在上下文的配置中设置代理来关闭自动生成代理

    context.Configuration.ProxyCreationEnabled = false;

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

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


4

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

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

1
显然看起来很简单但是对我
没用


4

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


3

提供的答案是好的,但是我认为可以通过添加“建筑”角度来改善它们。

调查中

MVC's Controller.Json函数正在执行此操作,但是在这种情况下提供相关错误非常困难。通过使用Newtonsoft.Json.JsonConvert.SerializeObject,错误确切指定了触发循环引用的属性是什么。当序列化更复杂的对象层次结构时,这特别有用。

适当的架构

永远不要尝试序列化数据模型(例如EF模型),因为ORM的导航属性是进行序列化时的绝妙之路。数据流应为以下内容:

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

可以使用自动映射器(例如Automapper)从数据模型中获取服务模型。尽管这不能保证缺少循环引用,但应进行适当的设计:服务模型应准确包含服务使用者的要求(即属性)。

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

现代应用程序趋向于避免立即加载复杂的数据结构,并且服务模型应该比较薄。例如:

  1. 访问事件-仅加载标头数据(标识符,名称,日期等)->仅包含标头数据的服务模型(JSON)
  2. 托管与会者列表-访问弹出菜单并延迟加载列表->仅包含与会者列表的服务模型(JSON)

1

我正在使用此修复程序,因为在MVC5视图中使用了淘汰赛。

行动中

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

0

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

private Object DeCircular(Object object)
{
   // Set properties that cause the circular reference to null

   return object
}

-1
//first: Create a class as your view model

public class EventViewModel 
{
 public int Id{get;set}
 public string Property1{get;set;}
 public string Property2{get;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);
}

这不能回答问题
Dane I

-1

解决此问题的一种更简单的替代方法是返回一个字符串,然后使用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 );
}

这有助于以较少的数据和所需的属性呈现视图,并使Web运行更快。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.