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

我试图做一个简单的 JSON 返回,但我有下面的问题。

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

我得到了一个 HTTP 500,但是在这个问题的标题中显示了一个例外

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

这也造成了同样的问题。

这是一个 bug 还是我的实现?

238684 次浏览

在您的对象层次结构中似乎有循环引用,而 JSON 序列化程序不支持这种引用。你需要所有的柱子吗?您只能在视图中获取所需的属性:

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

这将使您的 JSON 对象更轻巧,更容易理解。如果有许多属性,可以使用 自动绘图软件在 DTO 对象和 View 对象之间进行 自然而然地映射。

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

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

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

这实际上是因为复杂对象导致结果 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
);
}

这是因为新的 DbContext T4模板用于生成 EntityFramework 实体。为了能够执行变更跟踪,这个模板使用代理模式,用它们包装您的漂亮的 POCO。然后,在使用 JavaScriptSerializer 进行序列化时,这会导致问题。

那么这两个解决方案是:

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

    配置

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

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

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

我也有同样的问题,用 using Newtonsoft.Json;解决了

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


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

使用 Newtonsoft. Json: 在 Global.asax Application _ Start 方法中添加以下代码行:

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

我正在使用修复程序,因为在 MVC5视图中使用 Knokout。

开始行动

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

总而言之,有四种解决方案:

解决方案1: 关闭 DBContext 的 ProxyCreate 并最终恢复它。

    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: 通过在序列化程序设置中设置 ReferenceLoopProcessing 来忽略 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: 返回一个只包含所需属性的 Model。

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

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

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

调查

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

合适的建筑

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

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

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

在这些罕见的情况下,当客户端请求一个层次结构,该层次结构涉及不同层次上的相同对象类型时,服务可以创建一个具有父-> 子关系的线性结构(只使用标识符,而不使用引用)。

现代应用程序倾向于避免同时加载复杂的数据结构,而且服务模型应该更加简洁。例如:

  1. 访问只包含头数据的事件头数据(标识符、名称、日期等)-> 服务模型(JSON)
  2. 托管参与者列表-访问一个弹出窗口并延迟加载列表-> 只包含参与者列表的服务模型(JSON)

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

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


return object
}

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

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