gdereese wrote:
FRED.G wrote:
Hi gdereese,
I'm very interested in your project, but the link for downloading doesn't work on your blog.
Sorry about that...I've updated the blog article with a working link. Try it again and let me know if you have any more problems.
gdereese, I would like to thank you for the sample project you shared with us it was very informative, but in my opinion, it bears some room for improvement.
The custom collection you have defined is certainly observeable, but it does not support lazy loading. You compensate for this lack of lazy loading by making the objects inside of the collection lazy proxies, and unfortunately, this is unacceptable for my needs.
I found some code from Damon Carr that I liked better, located at:
http://damon.agilefactor.com/2007/07/nhibernate-custom-collections.html
But the code that he shares does not support the observable collection quality that you supplied.
I put the two of these examples together to make a lazy-loading observeable custom collection with NHibernate, and the code looks like this:
Code:
namespace NotifyingCollectionDemo.Library.Collections
{
public interface IDomainList<T> : IList<T>, IPersistentCollection, INotifyCollectionChanged
{
}
internal class DomainPersistentGenericBag<T> : PersistentGenericBag<T>, IDomainList<T>
{
public DomainPersistentGenericBag(ISessionImplementor session, IList<T> coll)
: base(session, coll)
{
}
public DomainPersistentGenericBag(ISessionImplementor session)
: base(session)
{
}
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>
/// Fires the <see cref="CollectionChanged"/> event to indicate an item has been
/// added to the end of the collection.
/// </summary>
/// <param name="item">Item added to the collection.</param>
protected void OnItemAdded(T item)
{
if (this.CollectionChanged != null)
{
this.CollectionChanged(this, new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, item, this.Count - 1));
}
}
/// <summary>
/// Fires the <see cref="CollectionChanged"/> event to indicate the collection
/// has been reset. This is used when the collection has been cleared or
/// entirely replaced.
/// </summary>
protected void OnCollectionReset()
{
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Reset));
}
}
/// <summary>
/// Fires the <see cref="CollectionChanged"/> event to indicate an item has
/// been inserted into the collection at the specified index.
/// </summary>
/// <param name="index">Index the item has been inserted at.</param>
/// <param name="item">Item inserted into the collection.</param>
protected void OnItemInserted(int index, T item)
{
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, item, index));
}
}
/// <summary>
/// Fires the <see cref="CollectionChanged"/> event to indicate an item has
/// been removed from the collection at the specified index.
/// </summary>
/// <param name="item">Item removed from the collection.</param>
/// <param name="index">Index the item has been removed from.</param>
protected void OnItemRemoved(T item, int index)
{
if (this.CollectionChanged != null)
{
this.CollectionChanged(this, new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, item, index));
}
}
#endregion
#region IList<T> members
public new void Add(T item)
{
base.Add(item);
this.OnItemAdded(item);
}
public new void Clear()
{
base.Clear();
this.OnCollectionReset();
}
public new void Insert(int index, T item)
{
base.Insert(index, item);
this.OnItemInserted(index, item);
}
public new bool Remove(T item)
{
int index = this.IndexOf(item);
bool result = base.Remove(item);
this.OnItemRemoved(item, index);
return result;
}
public new void RemoveAt(int index)
{
T item = this[index];
base.RemoveAt(index);
this.OnItemRemoved(item, index);
}
#endregion
}
public class DomainList<T> : List<T>, IUserCollectionType, IDomainList<T>
{
private IPersistentCollection m_EncapsulatedCollection;
private ISessionImplementor m_SessionImplementor;
private bool m_IsReadOnly;
public DomainList()
{
}
public DomainList(IList<T> collection, ISessionImplementor session)
{
InitializeFromExisting(collection, session);
}
#region IDomainList<T> Members
public void InitializeFromCache(ICollectionPersister persister, object disassembled, object owner)
{
m_EncapsulatedCollection.InitializeFromCache(persister, disassembled, owner);
}
public bool NeedsRecreate(ICollectionPersister persister)
{
return m_EncapsulatedCollection.NeedsRecreate(persister);
}
public IEnumerable Entries()
{
return m_EncapsulatedCollection.Entries();
}
public object ReadFrom(IDataReader reader, ICollectionPersister persister, ICollectionAliases descriptor,
object owner)
{
return m_EncapsulatedCollection.ReadFrom(reader, persister, descriptor, owner);
}
public object GetIndex(object entry, int i)
{
return m_EncapsulatedCollection.GetIndex(entry, i);
}
public object GetElement(object entry)
{
return m_EncapsulatedCollection.GetElement(entry);
}
public object GetSnapshotElement(object entry, int i)
{
return m_EncapsulatedCollection.GetSnapshotElement(entry, i);
}
public void BeforeInitialize(ICollectionPersister persister)
{
m_EncapsulatedCollection.BeforeInitialize(persister);
}
public bool EqualsSnapshot(IType elementType)
{
return m_EncapsulatedCollection.EqualsSnapshot(elementType);
}
public object Disassemble(ICollectionPersister persister)
{
return m_EncapsulatedCollection.Disassemble(persister);
}
public bool EntryExists(object entry, int i)
{
return m_EncapsulatedCollection.EntryExists(entry, i);
}
public bool NeedsInserting(object entry, int i, IType elemType)
{
return m_EncapsulatedCollection.NeedsInserting(entry, i, elemType);
}
public bool NeedsUpdating(object entry, int i, IType elemType)
{
return m_EncapsulatedCollection.NeedsUpdating(entry, i, elemType);
}
public ICollection GetDeletes(IType elemType, bool indexIsFormula)
{
return m_EncapsulatedCollection.GetDeletes(elemType, indexIsFormula);
}
public bool IsWrapper(object collection)
{
return m_EncapsulatedCollection.IsWrapper(collection);
}
public ICollection GetOrphans(object snapshot, Type entityName)
{
return m_EncapsulatedCollection.GetOrphans(snapshot, entityName);
}
public bool Empty
{
get { return m_EncapsulatedCollection.Empty; }
}
public void PostAction()
{
m_EncapsulatedCollection.PostAction();
}
public object GetValue()
{
return m_EncapsulatedCollection.GetValue();
}
public void BeginRead()
{
m_EncapsulatedCollection.BeginRead();
}
public bool EndRead(ICollectionPersister persister)
{
return m_EncapsulatedCollection.EndRead(persister);
}
public bool AfterInitialize(ICollectionPersister persister)
{
return m_EncapsulatedCollection.AfterInitialize(persister);
}
public bool UnsetSession(ISessionImplementor session)
{
return m_EncapsulatedCollection.UnsetSession(session);
}
public bool SetCurrentSession(ISessionImplementor session)
{
return m_EncapsulatedCollection.SetCurrentSession(session);
}
public ICollection GetSnapshot(ICollectionPersister persister)
{
return m_EncapsulatedCollection.GetSnapshot(persister);
}
public void ForceInitialization()
{
m_EncapsulatedCollection.ForceInitialization();
}
public void PreInsert(ICollectionPersister persister)
{
m_EncapsulatedCollection.PreInsert(persister);
}
public void AfterRowInsert(ICollectionPersister persister, object entry, int i)
{
m_EncapsulatedCollection.AfterRowInsert(persister, entry, i);
}
public object GetIdentifier(object entry, int i)
{
return m_EncapsulatedCollection.GetIdentifier(entry, i);
}
public bool IsSnapshotEmpty(ICollection snapshot)
{
return m_EncapsulatedCollection.IsSnapshotEmpty(snapshot);
}
public void ClearDirty()
{
m_EncapsulatedCollection.ClearDirty();
}
public void Dirty()
{
m_EncapsulatedCollection.Dirty();
}
public bool IsDirectlyAccessible
{
get { return m_EncapsulatedCollection.IsDirectlyAccessible; }
set { m_EncapsulatedCollection.IsDirectlyAccessible = value; }
}
public bool WasInitialized
{
get { return m_EncapsulatedCollection.WasInitialized; }
}
public bool HasQueuedAdds
{
get { return m_EncapsulatedCollection.HasQueuedAdds; }
}
public ICollection QueuedAddsCollection
{
get { return m_EncapsulatedCollection.QueuedAddsCollection; }
}
public ICollectionSnapshot CollectionSnapshot
{
get { return m_EncapsulatedCollection.CollectionSnapshot; }
set { m_EncapsulatedCollection.CollectionSnapshot = value; }
}
public object Owner
{
get { return m_EncapsulatedCollection.Owner; }
set { m_EncapsulatedCollection.Owner = value; }
}
public bool IsDirty
{
get { return m_EncapsulatedCollection.IsDirty; }
}
#endregion
// public IPersistentCollection InitializeFromExisting(IList<T> collection)
// {
// InitializeFromExisting(collection, m_SessionImplementor);
// return m_EncapsulatedCollection;
// }
public IPersistentCollection InitializeFromExisting(IList<T> collection, ISessionImplementor session)
{
if (collection == null)
throw new ArgumentNullException("collection");
if (session != null)
m_EncapsulatedCollection = new DomainPersistentGenericBag<T>(session, collection);
else
throw new NullReferenceException("No ISessionImplementor Set");
return m_EncapsulatedCollection;
}
#region IUserCollectionType Members
IPersistentCollection IUserCollectionType.Instantiate(ISessionImplementor session,
ICollectionPersister persister)
{
m_EncapsulatedCollection = new DomainPersistentGenericBag<T>(session);
m_SessionImplementor = session;
return m_EncapsulatedCollection;
}
IPersistentCollection IUserCollectionType.Wrap(ISessionImplementor session, object collection)
{
m_EncapsulatedCollection = new DomainPersistentGenericBag<T>(session, (DomainList<T>) collection);
m_SessionImplementor = session;
return m_EncapsulatedCollection;
}
IEnumerable IUserCollectionType.GetElements(object collection)
{
return (IEnumerable) collection;
}
bool IUserCollectionType.Contains(object collection, object entity)
{
return ((IList<T>) collection).Contains((T) entity);
}
object IUserCollectionType.IndexOf(object collection, object entity)
{
return ((IList<T>) collection).IndexOf((T) entity);
}
object IUserCollectionType.ReplaceElements(object original, object target, ICollectionPersister persister,
object owner, IDictionary copyCache, ISessionImplementor session)
{
IList<T> result = (IList<T>) target;
result.Clear();
foreach (T item in ((IList<T>) original))
result.Add(item);
return result;
}
object IUserCollectionType.Instantiate()
{
return new DomainList<T>();
}
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>
/// Fires the <see cref="CollectionChanged"/> event to indicate an item has been
/// added to the end of the collection.
/// </summary>
/// <param name="item">Item added to the collection.</param>
protected void OnItemAdded(T item)
{
if (this.CollectionChanged != null)
{
this.CollectionChanged(this, new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, item, this.Count - 1));
}
}
/// <summary>
/// Fires the <see cref="CollectionChanged"/> event to indicate the collection
/// has been reset. This is used when the collection has been cleared or
/// entirely replaced.
/// </summary>
protected void OnCollectionReset()
{
if (this.CollectionChanged != null)
{
this.CollectionChanged(this, new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Reset));
}
}
/// <summary>
/// Fires the <see cref="CollectionChanged"/> event to indicate an item has
/// been inserted into the collection at the specified index.
/// </summary>
/// <param name="index">Index the item has been inserted at.</param>
/// <param name="item">Item inserted into the collection.</param>
protected void OnItemInserted(int index, T item)
{
if (this.CollectionChanged != null)
{
this.CollectionChanged(this, new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, item, index));
}
}
/// <summary>
/// Fires the <see cref="CollectionChanged"/> event to indicate an item has
/// been removed from the collection at the specified index.
/// </summary>
/// <param name="item">Item removed from the collection.</param>
/// <param name="index">Index the item has been removed from.</param>
protected void OnItemRemoved(T item, int index)
{
if (this.CollectionChanged != null)
{
this.CollectionChanged(this, new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, item, index));
}
}
#endregion
#region IList<T> members
public new void Add(T item)
{
base.Add(item);
this.OnItemAdded(item);
}
public new void Clear()
{
base.Clear();
this.OnCollectionReset();
}
public new void Insert(int index, T item)
{
base.Insert(index, item);
this.OnItemInserted(index, item);
}
public new bool Remove(T item)
{
int index = this.IndexOf(item);
bool result = base.Remove(item);
this.OnItemRemoved(item, index);
return result;
}
public new void RemoveAt(int index)
{
T item = this[index];
base.RemoveAt(index);
this.OnItemRemoved(item, index);
}
#endregion
}
}
How to use this? Inside of your persistent objects you would have code like this:
Code:
private IDomainList<ListItem> _items = new DomainList<ListItem>();
...
public IDomainList<ListItem> Items
{
get { return this._items; }
set { this._items = value; }
}
and inside of your mapping files, you would use this:
Code:
<bag name="Items" inverse="true" cascade="all-delete-orphan" generic="true" lazy="true"
collection-type="NotifyingCollectionDemo.Library.Collections.DomainList`1[[NotifyingCollectionDemo.Library.DomainModel.ListItem, NotifyingCollectionDemo.Library]], NotifyingCollectionDemo.Library">
<key column="ListContainerID" />
<one-to-many class="ListItem" />
</bag>
The above code does work according to my tests, but still I am not happy. Do you know why? Look at the mapping syntax for collection-type: "`1[[..]]"
This is ugly, and it could become difficult to manage as things get more complex.
Why is it that when I use an IList<T> with a PersistentGenericBagthat I do not have to specify the data type of the collection class, but when I use my own custom collection (that inherits from PersistentGenericBag) I must specify this?