在序列化“SubSonic.Schema .DatabaseColumn”types的对象时检测到循环引用。
我正在尝试做一个简单的JSON返回,但我有问题,我有以下的下面。
public JsonResult GetEventData() { var data = Event.Find(x => x.ID != 0); return Json(data); }
我得到一个HTTP 500,除了这个问题的标题中显示的例外。 我也试过了
var data = Event.All().ToList()
这给了同样的问题。
这是一个错误还是我的实现?
看来你的对象层次结构中有循环引用,这是JSON序列化程序不支持的。 你需要所有的列吗? 你可以在视图中只拾取你需要的属性:
return Json(new { PropertyINeed1 = data.PropertyINeed1, PropertyINeed2 = data.PropertyINeed2 });
这将使你的JSON对象更轻,更容易理解。 如果你有很多属性,可以使用AutoMapper在DTO对象和View对象之间自动映射。
我有同样的问题,并通过using Newtonsoft.Json;
解决using Newtonsoft.Json;
var list = JsonConvert.SerializeObject(model, Formatting.None, new JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore }); return Content(list, "application/json");
这实际上是因为复杂的对象是导致json对象失败的原因。 而且它失败了,因为当这个对象被映射的时候,它映射了映射他们父母的孩子,做一个循环引用。 Json需要无限的时间来序列化它,所以它可以防止exception的问题。
entity framework映射也产生相同的行为,解决scheme是放弃所有不需要的属性。
只是说明最后的答案,整个代码将是:
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 ); }
像xml和其他各种格式的JSON是基于树的序列化格式。 如果你的对象中有循环引用,它就不会爱你,因为“树”是:
root B => child A => parent B => child A => parent B => ...
通常有沿某一path禁用导航的方法; 例如,使用XmlSerializer
您可以将父项属性标记为XmlIgnore
。 我不知道如果这是可能的问题的JSON序列化,也不知道DatabaseColumn
是否有适当的标记( 非常不可能,因为它需要引用每个序列化API)
总结起来,有三个解决scheme:
private DBEntities db = new DBEntities();//dbcontext //Solution 1: turn off ProxyCreation for the DBContext and restore it in the end 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; } } //Solution 2: Using JsonConvert by Setting ReferenceLoopHandling to ignore on the serializer settings. //using using Newtonsoft.Json; 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); } } //Solution 3: return a new dynamic object which includes only the needed properties. 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); } }
这是因为用于生成EntityFramework实体的新的DbContext T4模板。 为了能够执行更改跟踪,这个模板使用代理模式,通过包装你的漂亮的POCO。 这会在使用JavaScriptSerializer序列化时引发问题。
那么这两个解决scheme是:
- 要么只是序列化并返回客户端上需要的属性
-
您可以通过设置上下文的configuration来closures代理的自动生成
context.Configuration.ProxyCreationEnabled = false;
在下面的文章很好的解释。
http://juristr.com/blog/2011/08/javascriptserializer-circular-reference/
使用Newtonsoft.Json:在你的Global.asax Application_Start方法中添加这一行:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
将[JsonIgnore]
添加到模型中的虚拟属性。
避免直接转换表格对象。 如果关系在其他表之间设置,它可能会抛出这个错误。 相反,您可以创build一个模型类,将值分配给类对象,然后对其进行序列化。
提供的答案是好的,但我认为他们可以通过增加一个“架构”的angular度来改善。
调查
MVC's Controller.Json
函数正在做这个工作,但在这种情况下提供相关的错误是非常糟糕的。 通过使用Newtonsoft.Json.JsonConvert.SerializeObject
,该错误确切指定触发循环引用的属性是什么。 序列化更复杂的对象层次结构时,这是特别有用的。
适当的架构
我们不应该尝试序列化数据模型(例如EF模型),因为ORM的导航属性是序列化过程中的灭亡之路。 数据stream应该如下:
Database -> data models -> service models -> JSON string
可以使用自动映射器(例如Automapper )从数据模型中获取服务模型。 虽然这并不能保证缺less循环引用,但正确的devise应该做到这一点:服务模型应该包含服务使用者需要的东西(即属性)。
在极less数情况下,当客户端请求在不同级别上涉及相同对象types的层次结构时,该服务可以创build具有父 – 关关系的线性结构(仅使用标识符而不是引用)。
现代应用程序倾向于避免一次加载复杂的数据结构,服务模型应该很小。 例如:
- 访问仅包含事件的头数据(标识符,名称,date等) – >仅包含头数据的服务模型(JSON)
- 受pipe参与者列表 – 访问popup窗口并延迟加载列表 – >仅包含与会者列表的服务模型(JSON)
我正在使用修补程序,因为在MVC5视图中使用Knockout。
在行动
return Json(ModelHelper.GetJsonModel<Core_User>(viewModel));
function
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_; }
您可以注意到引起循环引用的属性。 然后你可以做一些事情:
private Object DeCircular(Object object) { // Set properties that cause the circular reference to null return object }
//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); }
解决这个问题的一个更简单的方法是返回一个string,并使用JavaScriptSerializer将该string格式化为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 ); }
“select”部分非常重要,您可以在视图中select所需的属性。 一些对象有一个父母的参考。 如果您不select属性,则可能会出现循环引用,如果只是将表格作为一个整体。
不要这样做:
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 ); }
这有助于以更less的数据显示视图,只需使用所需的属性,并使networking运行更快。