如何解决JSON和Entity的循环引用问题


13

我一直在尝试创建一个网站,该网站将MVC和JSON用于我的表示层,以及用于数据模型/数据库的Entity框架。我的问题与将模型对象序列化为JSON有关。

我正在使用代码优先方法创建数据库。在执行代码优先方法时,一对多关系(父/子)要求子对父有引用。(示例代码是我的错字,但您会看到图片)

class parent
{
   public List<child> Children{get;set;}
   public int Id{get;set;}

}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId")]
    public parent MyParent{get;set;}
    public string name{get;set;}
 }

通过JsonResult返回“父”对象时,由于“子”具有父类的属性,因此引发循环引用错误。

我已经尝试了ScriptIgnore属性,但是无法查看子对象。在某些时候,我将需要在父子视图中显示信息。

我试图为没有循环引用的父级和子级创建基类。不幸的是,当我尝试发送baseParent和baseChild时,它们被JSON分析器读取为它们的派生类(我很确定此概念正在使我逃避)。

Base.baseParent basep = (Base.baseParent)parent;
return Json(basep, JsonRequestBehavior.AllowGet);

我想出的一个解决方案是创建“查看”模型。我创建数据库模型的简单版本,其中不包含对父类的引用。这些视图模型每个都有返回数据库版本的方法和一个将数据库模型作为参数的构造函数(viewmodel.name = databasemodel.name)。尽管此方法有效,但似乎是强制的。

注意:我在这里发布是因为我认为这值得讨论。我可以利用其他设计模式来解决此问题,也可以像在模型上使用其他属性一样简单。在搜索中,我没有找到克服此问题的好方法。

我的最终目标是拥有一个很好的MVC应用程序,该应用程序充分利用JSON与服务器进行通信并显示数据。同时跨层维护一致的模型(或尽我所能)。

Answers:


6

我在您的问题中看到两个不同的主题:

  • 序列化为JSON时如何管理循环引用?
  • 在视图中将EF实体用作模型实体有多安全?

关于循环引用,很遗憾地说,没有简单的解决方案。首先,因为JSON无法用于表示循环引用,所以下面的代码:

var aParent = {Children : []}, aChild  = {Parent : aParent};
aParent.Children.push(aChild);
JSON.stringify(aParent);

结果是: TypeError: Converting circular structure to JSON

您唯一的选择是只保留组合的->组成部分,并丢弃“后向导航”组件->组合,因此在您的示例中:

class parent
{
    public List<child> Children{get;set;}
    public int Id{get;set;}
}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId"), ScriptIgnore]
    public parent MyParent{get;set;}
    public string name{get;set;}
}

没有什么可以阻止您在客户端使用jQuery重新构造此导航属性的:

$.each(parent.Children, function(i, child) {
  child.Parent = parent;  
})

但是然后您将需要再次丢弃它,然后再将其发送回服务器,因为JSON.stringify将无法序列化循环引用:

$.each(parent.Children, function(i, child) {
  delete child.Parent;  
})

现在,存在使用EF实体作为视图模型实体的问题。

First EF可能会使用您班级的动态代理来实现诸如更改检测或延迟加载之类的行为,如果要序列化EF实体,则必须禁用它们。

此外,在UI中使用EF实体可能会面临风险,因为所有默认绑定程序都会将请求中的每个字段映射到实体字段,包括您不希望用户设置的实体字段。

因此,如果您希望正确设计MVC应用程序,我建议使用专用的视图模型,以防止内部业务模型的“胆量”暴露给客户,因此,我建议您使用特定的视图模型。


使用面向对象的技术是否有一种理想的方法,我可以解决循环引用和EF问题。
DanScan

我可以绕过循环引用和EF问题找到一种面向对象技术的理想方法吗?就像BaseObject是由objectObject和viewObject继承的。因此,entityObject将具有循环引用,而viewObject将不具有循环引用。我已经通过从entityObject(viewObject.name = entityObject.name)构建viewObject来解决了这个问题,但这似乎是在浪费时间。我该如何解决这个问题?
DanScan

他们非常你。您的解释非常清楚且易于理解。
尼克

2

尝试序列化对象的一种更简单的选择是禁用父/子对象的序列化。相反,您可以单独调用以在需要时获取关联的父/子对象。这对于您的应用程序可能不是理想的选择,但这是一个选择。

为此,您可以设置一个DataContractSerializer并将数据模型类的构造函数中的DataContractSerializer.PreserveObjectReferences属性设置为'false'。这指定在序列化HTTP响应时不应保留对象引用。

例子:

Json格式:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.None;

XML格式:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Employee), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ false, null);
xml.SetSerializer<Employee>(dcs);

这意味着,如果您获取引用了子对象的项目,则这些子对象不会被序列化。

另请参见DataContractsSerializer类。


1

处理循环引用的JSON序列化器

这是一个自定义Jackson的示例JSONSerializer,它通过序列化第一个匹配项并reference在所有后续匹配项上存储* 到第一个匹配项来处理循环引用。

用Jackson序列化对象时处理循环引用

以上文章的相关部分摘要:

private final Set<ObjectName> seen;

/**
 * Serialize an ObjectName with all its attributes or only its String representation if it is a circular reference.
 * @param on ObjectName to serialize
 * @param jgen JsonGenerator to build the output
 * @param provider SerializerProvider
 * @throws IOException
 * @throws JsonProcessingException
 */
@Override
public void serialize(@Nonnull final ObjectName on, @Nonnull final JsonGenerator jgen, @Nonnull final SerializerProvider provider) throws IOException, JsonProcessingException
{
    if (this.seen.contains(on))
    {
        jgen.writeString(on.toString());
    }
    else
    {
        this.seen.add(on);
        jgen.writeStartObject();
        final List<MBeanAttributeInfo> ais = this.getAttributeInfos(on);
        for (final MBeanAttributeInfo ai : ais)
        {
            final Object attribute = this.getAttribute(on, ai.getName());
            jgen.writeObjectField(ai.getName(), attribute);
        }
        jgen.writeEndObject();
    }
}

0

我想出的一个解决方案是创建“查看”模型。我创建数据库模型的简单版本,其中不包含对父类的引用。这些视图模型每个都有返回数据库版本的方法和一个将数据库模型作为参数的构造函数(viewmodel.name = databasemodel.name)。尽管此方法有效,但似乎是强制的。

发出最少的数据是唯一正确的答案。当您从数据库中发送数据时,通常没有意义的是发送具有所有关联的每一列。使用者不需要处理数据库的关联和结构,即针对数据库。这样不仅可以节省带宽,而且维护,读取和使用也更容易。查询数据,然后为实际需要发送eq的数据建模。最低限度。


在谈论大数据时,需要更多的处理时间,因为现在您必须对所有数据进行两次转换。
David van Dugteren

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.