Мне потребовалось много времени, чтобы получить его, но я думаю, что ответ на мою проблему на самом деле обманчиво прост. Наилучший подход, который долгое время поддерживался командой Hibernate, это просто не переопределять equals и gethashcode. Что я не получил, так это то, что когда я вызываю Contains для набора бизнес-объектов, очевидно, я хочу знать, содержит ли этот набор объект с определенной бизнес-ценностью. Но это было то, чего я не получил от набора настойчивости Nhibernate. Но Стефан Штайнеггер исправил это в комментарии к другому вопросу на эту тему, который я задавал: «набор постоянства - это не бизнес-коллекция»! Я совершенно не смог понять его замечание с первого раза.
Ключевая проблема заключалась в том, что я не должен пытаться настроить постоянство на поведение как бизнес-коллекцию. Вместо этого я должен использовать набор постоянства, завернутый в бизнес-коллекцию. Тогда все становится намного проще. Итак, в своем коде я создал оболочку:
internal abstract class EntityCollection<TEnt, TParent> : IEnumerable<TEnt>
{
private readonly Iesi.Collections.Generic.ISet<TEnt> _set;
private readonly TParent _parent;
private readonly IEqualityComparer<TEnt> _comparer;
protected EntityCollection(Iesi.Collections.Generic.ISet<TEnt> set, TParent parent, IEqualityComparer<TEnt> comparer)
{
_set = set;
_parent = parent;
_comparer = comparer;
}
public IEnumerator<TEnt> GetEnumerator()
{
return _set.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public bool Contains(TEnt entity)
{
return _set.Any(x => _comparer.Equals(x, entity));
}
internal Iesi.Collections.Generic.ISet<TEnt> GetEntitySet()
{
return _set;
}
internal protected virtual void Add(TEnt entity, Action<TParent> addParent)
{
if (_set.Contains(entity)) return;
if (Contains(entity)) throw new CannotAddItemException<TEnt>(entity);
_set.Add(entity);
addParent.Invoke(_parent);
}
internal protected virtual void Remove(TEnt entity, Action<TParent> removeParent)
{
if (_set.Contains(entity)) return;
_set.Remove(entity);
removeParent.Invoke(_parent);
}
}
Это универсальная оболочка, которая реализует бизнес-значение набора. Он знает, когда два бизнес-объекта равны по значению через IEqualityComparer, он представляет себя как истинную бизнес-коллекцию, представляющую сущность в виде множества интерфейсов сущностей (гораздо чище, чем выставление набора постоянства), и даже знает, как обрабатывать двунаправленные отношения родитель.
Родительский объект, которому принадлежит эта бизнес-коллекция, имеет следующий код:
public virtual IEnumerable<IProduct> Products
{
get { return _products; }
}
public virtual Iesi.Collections.Generic.ISet<Product> ProductSet
{
get { return _products.GetEntitySet(); }
protected set { _products = new ProductCollection<Brand>(value, this); }
}
public virtual void AddProduct(IProduct product)
{
_products.Add((Product)product, ((Product)product).SetBrand);
}
public virtual void RemoveProduct(IProduct product)
{
_products.Remove((Product)product, ((Product)product).RemoveFromBrand);
}
Таким образом, у сущности на самом деле есть два интерфейса: бизнес-интерфейс, представляющий бизнес-коллекцию, и интерфейс сущности, который предоставляется Nhibernate для обеспечения устойчивости коллекции. Обратите внимание, что в Nhibernate возвращается тот же набор сохраняемости, что и при использовании свойства ProductSet.
В основном все сводится к разделению интересов:
- набор персистентности не моя забота, но nhibernate обрабатывает, чтобы сохранить мою коллекцию
- бизнес-смысл равенства по значению обрабатывается средствами сравнения равенства
- бизнес-значение набора, т. Е. Когда набор уже содержит сущность с одинаковым бизнес-значением, я не смогу передать второй другой объект с таким же бизнес-значением, который обрабатывается объектом бизнес-коллекции.
Только когда я хочу смешивать объекты между сеансами, мне придется прибегать к другим решениям, как упомянуто выше. Но я думаю, что если вы можете избежать этой ситуации, вы должны.