映射和序列化双向关联

本文关键字:关联 序列化 映射 | 更新日期: 2023-09-27 18:11:09

我以为这是一个微不足道的问题,马上就能找到答案,但不知何故,互联网证明了我的假设是错误的。

我有一个包含多个实体的模型,这些实体之间具有所有可能的关联(1对1、1对多、多对多、聚合、组合等)。为了简单起见,让我们举个例子。我们有Person类和Car类。一个人可以拥有多辆车,但一辆车只能属于一个人(所以是1对多关系)。现在,在Person中创建List/ArrayList来跟踪他/她的汽车将非常简单。不过,我也想了解一下车主的情况。所以,类看起来像这样:

[Serializable]
public class Person
{
    [XmlElement]
    public List<Car> Cars { get; set; }
    [XmlAttribute]
    public string Name { get; set; }
    public Person()
    {
        Cars = new List<Car>();
    }
}
[Serializable]
public class Car
{
    [XmlElement]
    public Person Owner { get; set; }
    [XmlAttribute]
    public string Type { get; set; }
    public Car()
    {
    }
}
但是,我认为这种结构会导致Xml文件中的无限循环,如下所示:
<Person name="John Doe">
  <Cars>
    <Car type="Ford">
      <Owner>
        <Person name="John Doe">
          <Cars>
            <Car type="Ford">
              <Owner>
                <Person name="John Doe">
      ... etc.

我甚至尝试过,在序列化期间,我得到了"您需要将XmlChoiceIdentifierAttribute添加到'Owner'成员"异常。

我有几个问题:1. 有没有一种方法可以防止序列化中的循环?2. 如果没有,我是否必须为每个类编写自己的序列化器?3.这个映射可以吗?还是有其他更好的方法?我考虑过一个中央"映射器"类,它将根据ID返回所需的对象……但话又说回来,这可以通过SQL来完成。我想避免SQL(因为保持应用程序轻量级)。

映射和序列化双向关联

XmlSerializer是树序列化器,而不是图序列化器。最好的办法是在序列化过程中避免向后导航,例如:

[XmlIgnore]
public Person Owner { get; set; }

("parent"answers"owner"几乎都是反向导航)

不幸的是,XmlSerializer不支持序列化后回调,否则可以添加如下内容:

[WhateverOnAfterDeserialized]
public void OnAfterDeserialized(...) {
    foreach(var car in cars) car.Owner = this;
}

其他一些序列化器确实支持序列化回调,但是由于这个原因,这些其他序列化器也可能支持全图序列化。例如,DataContractSerializer既可以支持回调,也可以支持完整的图形,但它对xml提供的控制要少得多。

另一个选择是有一个自定义集合类型,在添加/删除时维护父属性;例如:

public class Person
{
    private readonly CarCollection cars;
    public Person() {
        cars = new CarCollection(this);
    }
    [XmlElement]
    public CarCollection Cars { get { return cars; } } 
    ...
}

with (in CarCollection):

// add code...
innerList.Add(value);
value.Parent = parent; // this is the field stored in the constructor

就我个人而言,我认为这可能太过分了。

另一种选择是简单地添加一个在反序列化之后调用的修复方法,它可以做任何必要的事情(并向下级联):

public void FixupAfterDeserializer() {
    foreach(var car in cars) car.Parent = this;
}

请注意,您必须手动调用它。

最后,注意XmlSerializer不需要[Serializable]