避免类的循环引用

本文关键字:循环 引用 | 更新日期: 2023-09-27 18:13:23

我有一个创建并返回一个"Person"的函数。"人"拥有"配偶"财产,当然,这是另一个"人"。这将导致无限递归,因为每次调用函数时创建的"Person"总是一个"新"对象。是否有一种方法可以使用相同的函数(如下所示)而不会导致无限循环?

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. 使用左外连接选择存储过程中配偶的属性。这将执行得更好,如果您想要选择所有员工,这将更有效,因为它将只是一次数据库访问。您可以在if语句中添加如下代码来构建配偶对象:

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

不完全符合OPs的问题,但既然我是因为标题而来到这里,那么添加一个一般的答案可能会有所帮助。

所以在我的场景中,我需要改变整个图中的一个属性设置它的所有子属性为任意深度

为了确保实例不会执行两次(从而允许StackOverflowException),我在每次调用中传递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的构造函数。