避免 类 的循环引用
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 );
}
我可以想到几个不同的选项:
向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;
}
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
的构造函数。
我有一个函数可以创建 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 );
}
我可以想到几个不同的选项:
向
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; }
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
的构造函数。