开始使用Nhibernate来实现持久性,受到它尊重域模型的承诺的诱惑,我尝试为我的域对象实现一个关系管理器。基本上,为了在管理双向一对多和多对多关系方面DRY我的代码,我决定让这些关系由一个单独的类管理。当设置一对多或多对一属性时,在字典中为这两个对象创建一个条目,键要么是具有保存多面集合值的单面键,要么是具有保存多面集合值的多面键。
特定类型组合的一对多关系如下所示:
public class OneToManyRelation<TOnePart, TManyPart> : IRelation<IRelationPart, IRelationPart>
where TOnePart : class, IRelationPart
where TManyPart : class, IRelationPart
{
private readonly IDictionary<TOnePart, Iesi.Collections.Generic.ISet<TManyPart>> _oneToMany;
private readonly IDictionary<TManyPart, TOnePart> _manyToOne;
public OneToManyRelation()
{
_manyToOne = new ConcurrentDictionary<TManyPart, TOnePart>();
_oneToMany = new ConcurrentDictionary<TOnePart, Iesi.Collections.Generic.ISet<TManyPart>>();
}
public void Set(TOnePart onePart, TManyPart manyPart)
{
if (onePart == null || manyPart == null) return;
if (!_manyToOne.ContainsKey(manyPart)) _manyToOne.Add(manyPart, onePart);
else _manyToOne[manyPart] = onePart;
}
public void Add(TOnePart onePart, TManyPart manyPart)
{
if (onePart == null || manyPart == null) return;
if (!_manyToOne.ContainsKey(manyPart)) _manyToOne.Add(manyPart, onePart);
else _manyToOne[manyPart] = onePart;
if (!_oneToMany.ContainsKey(onePart)) _oneToMany.Add(onePart, new HashedSet<TManyPart>());
_oneToMany[onePart].Add(manyPart);
}
public Iesi.Collections.Generic.ISet<TManyPart> GetManyPart(TOnePart onePart)
{
if (!_oneToMany.ContainsKey(onePart)) _oneToMany[onePart] = new HashedSet<TManyPart>();
return _oneToMany[onePart];
}
public TOnePart GetOnePart(TManyPart manyPart)
{
if(!_manyToOne.ContainsKey(manyPart)) _manyToOne[manyPart] = default(TOnePart);
return _manyToOne[manyPart];
}
public void Remove(TOnePart onePart, TManyPart manyPart)
{
_manyToOne.Remove(manyPart);
_oneToMany[onePart].Remove(manyPart);
}
public void Set(TOnePart onePart, Iesi.Collections.Generic.ISet<TManyPart> manyPart)
{
if (onePart == null) return;
if (!_oneToMany.ContainsKey(onePart)) _oneToMany.Add(onePart, manyPart);
else _oneToMany[onePart] = manyPart;
}
public void Clear(TOnePart onePart)
{
var list = new HashedSet<TManyPart>(_oneToMany[onePart]);
foreach (var manyPart in list)
{
_manyToOne.Remove(manyPart);
}
_oneToMany.Remove(onePart);
}
public void Clear(TManyPart manyPart)
{
if (!_manyToOne.ContainsKey(manyPart)) return;
if (_manyToOne[manyPart] == null) return;
_oneToMany[_manyToOne[manyPart]].Remove(manyPart);
_manyToOne.Remove(manyPart);
}
}
左边的代码片段如下:
public virtual SubstanceGroup SubstanceGroup
{
get { return RelationProvider.SubstanceGroupSubstance.GetOnePart(this); }
protected set { RelationProvider.SubstanceGroupSubstance.Set(value, this); }
}
在一边,在这个例子中是SubstanceGroup,代码片段看起来像:
public virtual ISet<Substance> Substances
{
get { return RelationProvider.SubstanceGroupSubstance.GetManyPart(this); }
protected set { RelationProvider.SubstanceGroupSubstance.Set(this, value); }
}
只是使用我的域对象,这工作得很好。在域对象中,我只需要引用一个检索适当关系的抽象工厂,我可以从一侧设置关系,这样就自动变成双向的。
然而,当NH起作用时,问题是我在字典中得到了重复的键。以某种方式,NH设置了一个带有空值(!)的关系属性,并带有域对象的新副本(?)。因此,当域对象被保存时,我有该域对象的两个条目,例如关系的多面,即_manyToOne字典。
这个问题让我失去了我的头发,我不明白发生了什么??
回答你的第一个非常普遍的问题:"NHibernate真的提供透明的持久性吗?"我只能说:没有什么是完美的。NH尽其所能地保持透明,同时也尽可能地降低其复杂性。
这里有一些假设,特别是关于集合:集合和它们的实现不被认为是域模型的一部分。NH提供了自己的集合实现。您不仅需要使用像ISet
和IList
这样的接口。当从数据库中读取对象时,还应该使用NH给出的实例,并且永远不要用自己的实例替换它。(我不知道你的关系类实际上是用来做什么的,所以我不知道这是否是这里的问题。)
域对象在会话的同一实例中是唯一的。如果每次都获得域对象的新实例,则可能实现了"每次调用会话"反模式,该模式为每次数据库交互创建一个新会话。
我不知道你到底在做什么。这个OneToManyRelation的实际用途是什么?当NH不像预期的那样运行时你会怎么做?除了对'复杂的代码'和'你到底在做什么'的评论。问题是我正在替换NH的持久性集合,如下面的代码片段所示:
public void Add(TOnePart onePart, TManyPart manyPart)
{
if (onePart == null || manyPart == null) return;
if (!_manyToOne.ContainsKey(manyPart)) _manyToOne.Add(manyPart, onePart);
else _manyToOne[manyPart] = onePart;
if (!_oneToMany.ContainsKey(onePart)) _oneToMany.Add(onePart, new HashedSet<TManyPart>());
_oneToMany[onePart].Add(manyPart);
}
我为许多部分创建了一个新的哈希集。这就是问题所在。如果只设置了集合进入的许多部分(在NH的持久性集合实现的情况下),那么它就可以工作了。
作为一个NH新手,用NH的特殊实现替换集合是错误的一个重要来源。作为对其他NH新手的警告。