避免 类 的循环引用

Avoid circular reference for classes

我有一个函数可以创建 returns 一个 "Person"。 "Person" 有一个 "Spouse" 属性,这当然是另一个 "Person"。这会导致无限递归,因为每次调用函数时创建的 "Person" 始终是 "new"。有没有办法使用相同的函数(如下所示)而不会导致死循环?

public PersonModel Load(int personID)
{
    PersonModel person = new PersonModel();
    using (SqlConnection conn = new SqlConnection())
    {
        conn.ConnectionString = Helpers.ConnectionDB;
        SqlCommand command = new SqlCommand();
        command.Connection = conn;
        command.CommandType = CommandType.StoredProcedure;
        command.CommandText = "LoadPerson";
        command.Parameters.Add(new SqlParameter("@PersonID", personID));
        conn.Open();
        SqlDataReader reader = command.ExecuteReader();
        if (reader.Read())
        {
            person.PersonID = int.Parse(reader["PersonID"].ToString());
            person.FirstName = reader["FirstName"].ToString();
            person.LastName = reader["LastName"].ToString();
            person.MiddleName = reader["MiddleName"].ToString();
            person.Age = reader["Age"] != DBNull.Value ? int.Parse(reader["Age"].ToString()) : (int?)null;
            person.SpouseID = reader["SpouseID"] != DBNull.Value ? int.Parse(reader["SpouseID"].ToString()) : (int?)null;
            if (person.SpouseID != null && person.Spouse == null)
            {
                person.Spouse = this.Load(person.SpouseID.Value);
            }
        }
        conn.Close();
    }
    return person;
}

使用 Dictionary<Int32,PersonModel> 跟踪加载的实体:

public PersonModel Load(Dictionary<Int32,PersonModel> dict, int personId) {

    PersonModel ret;
    if( dict.TryGetValue( personId, out ret ) ) return ret;

    // load from database here, but do not call recursively just yet
    ret = new PersonModel() { ... };

    dict.Add( ret.PersonId, ret );

    ret.Spouse = this.Load( dict, person.SpouseId.Value );
}

我可以想到几个不同的选项:

  1. Load()添加一个可选参数以指示您不应尝试加载配偶。这有点 hack,但非常简单。

    public PersonModel Load(int personID, bool loadSpouse = true)
    {
        ...
        if (loadSpouse && person.SpouseID != null && person.Spouse == null)
        {
            person.Spouse = this.Load(person.SpouseID.Value, false);
        }
        ...
    
        return person;
    }
    
  2. Select 使用左外连接在存储过程中配偶的属性。这会表现得更好,如果你想 select 所有员工,这会更有效率,因为它只是一个数据库访问。您可以在 if 语句中添加这样的代码来构建配偶对象:

    person.Spouse = new PersonModel()
    person.Spouse.FirstName = reader["SpouseFirstName"].ToString();
    ...
    

不完全符合 OP 的问题,但由于我是因为标题才来到这里的,因此添加一个通用的答案可能会有所帮助。

所以在我的场景中,我需要更改整个图表中的 属性,将其所有子属性设置为任意深度。

为了确保我不会让一个实例执行两次(因此允许 WhosebugException),我在每次调用时都传递了一个 HashSet<T>,所以我们知道要跳过执行的项目:

//The executed parameter needn't ever be provided externally.
static void SetState(IObject obj, State state, HashSet<IObject> executed = null)
{
  if(executed == null) executed = new HashSet<IObject>(/* provide comparer if needed*/);
  if(!executed.Add(obj)) return;

  //safe to continue walking graph
  obj.State = state;
  var props = obj.GetObjectPropertyValues().OfType<IObject>();
  foreach (var prop in props)
    SetState(obj, state, executed);
}

请注意,这些项目必须实现适当的相等性和 GetHashCode 函数,或者应该将比较器参数传递给 HashSet 的构造函数。