IPagedList <T> implementation on my models using NHibernate
I found that when using NHibernate and creating a one-to-many relationship on an object, when many become very large, it can slow down dramatically. Now I have methods in my repository for collecting a programmed IList of this type, however, I would prefer to use these methods for the model, because often where other developers will first collect a list of child objects.
eg.
RecipientList.Recipients will return each recipient in the list.
I would like to implement a way to add paging in all my applications to many relationships in my models, using preferably the interface, but in fact everything that will not force typed relationships to the model. For example, it would be nice to have the following interface:
public interface IPagedList<T> : IList<T> { int Count { get; } IList<T> GetPagedList(int pageNo, int pageSize); IList<T> GetAll(); } Then you can use it in code ...
IList<Recipient> recipients = RecipientList.Recipients.GetPagedList(1, 400); I'm trying to think about how to do this without giving the model any understanding of paging, but right now I'm pushing my head against a brick wall.
In any case, can I implement the interface in the same way as NHibernate for IList and lazyloading currently? I lack NHibernate knowledge to know.
Does this even carry out a good idea? Your thoughts would be appreciated as the only .NET developer in the house. I have nothing to give up ideas.
UPDATE
The post below pointed me to an NHibernate custom collection attribute that would work well. However, I'm not sure what is the best way around this, I tried to inherit from the PersistentGenericBag, so it has the same basic IList functions without much effort, however I'm not sure how to compile a list of objects based on ISessionImplementor, I need to know how either:
- Get some ICriteria detail for the current IList that I have to populate.
- Get mapping data for a specific property associated with IList so that I can create my own ICriteria.
However, I'm not sure if I can do one of the above?
thanks
Well, I'm going to post this as an answer because it does basically what I wanted. However, I would like to get some feedback, as well as perhaps an answer to my one recommendation of a solution:
I created an interface called IPagedList.
public interface IPagedList<T> : IList<T>, ICollection { IList<T> GetPagedList(int pageNo, int pageSize); } Then a base class is created that it inherits from IPagedList:
public class PagedList<T> : IPagedList<T> { private List<T> _collection = new List<T>(); public IList<T> GetPagedList(int pageNo, int pageSize) { return _collection.Take(pageSize).Skip((pageNo - 1) * pageSize).ToList(); } public int IndexOf(T item) { return _collection.IndexOf(item); } public void Insert(int index, T item) { _collection.Insert(index, item); } public void RemoveAt(int index) { _collection.RemoveAt(index); } public T this[int index] { get { return _collection[index]; } set { _collection[index] = value; } } public void Add(T item) { _collection.Add(item); } public void Clear() { _collection.Clear(); } public bool Contains(T item) { return _collection.Contains(item); } public void CopyTo(T[] array, int arrayIndex) { _collection.CopyTo(array, arrayIndex); } int Count { get { return _collection.Count; } } public bool IsReadOnly { get { return false; } } public bool Remove(T item) { return _collection.Remove(item); } public IEnumerator<T> GetEnumerator() { return _collection.GetEnumerator(); } int ICollection<T>.Count { get { return _collection.Count; } } IEnumerator IEnumerable.GetEnumerator() { return _collection.GetEnumerator(); } public void CopyTo(Array array, int index) { T[] arr = new T[array.Length]; for (int i = 0; i < array.Length ; i++) { arr[i] = (T)array.GetValue(i); } _collection.CopyTo(arr, index); } int ICollection.Count { get { return _collection.Count; } } // The IsSynchronized Boolean property returns True if the // collection is designed to be thread safe; otherwise, it returns False. public bool IsSynchronized { get { return false; } } public object SyncRoot { get { return this; } } } Then I create an IUserCollectionType for NHibernate to use as the type of the user collection and NHPagedList, which inherits from the PersistentGenericBag, IPagedList as the actual collection. I created two separate classes for them, because using IUserCollectionType did not affect the actual collection, which will be used at all, so I kept the two parts of the logic separately. The code below is for both of the above:
public class PagedListFactory<T> : IUserCollectionType { public PagedListFactory() { } #region IUserCollectionType Members public bool Contains(object collection, object entity) { return ((IList<T>)collection).Contains((T)entity); } public IEnumerable GetElements(object collection) { return (IEnumerable)collection; } public object IndexOf(object collection, object entity) { return ((IList<T>)collection).IndexOf((T)entity); } public object Instantiate(int anticipatedSize) { return new PagedList<T>(); } public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister) { return new NHPagedList<T>(session); } public object ReplaceElements(object original, object target, ICollectionPersister persister, object owner, IDictionary copyCache, ISessionImplementor session) { IList<T> result = (IList<T>)target; result.Clear(); foreach (object item in ((IEnumerable)original)) { result.Add((T)item); } return result; } public IPersistentCollection Wrap(ISessionImplementor session, object collection) { return new NHPagedList<T>(session, (IList<T>)collection); } #endregion } NHPagedList next:
public class NHPagedList<T> : PersistentGenericBag<T>, IPagedList<T> { public NHPagedList(ISessionImplementor session) : base(session) { _sessionImplementor = session; } public NHPagedList(ISessionImplementor session, IList<T> collection) : base(session, collection) { _sessionImplementor = session; } private ICollectionPersister _collectionPersister = null; public NHPagedList<T> CollectionPersister(ICollectionPersister collectionPersister) { _collectionPersister = collectionPersister; return this; } protected ISessionImplementor _sessionImplementor = null; public virtual IList<T> GetPagedList(int pageNo, int pageSize) { if (!this.WasInitialized) { IQuery pagedList = _sessionImplementor .GetSession() .CreateFilter(this, "") .SetMaxResults(pageSize) .SetFirstResult((pageNo - 1) * pageSize); return pagedList.List<T>(); } return this .Skip((pageNo - 1) * pageSize) .Take(pageSize) .ToList<T>(); } public new int Count { get { if (!this.WasInitialized) { return Convert.ToInt32(_sessionImplementor.GetSession().CreateFilter(this, "select count(*)").List()[0].ToString()); } return base.Count; } } } You will notice that it will check whether the collection was initialized or not, so that we know when to check the database for the paged list or when to simply use the current memory objects.
Now you're ready to go, just change the current IList links on your models to IPagedList, and then move NHibernate to the new collection using the fluent NHibernate below, and you're ready to go.
.CollectionType<PagedListFactory<Recipient>>() This is the first iteration of this code, so some refactoring and modifications will be required to improve it.
My only problem at the moment is that it will not receive paged items in the order in which the mapping file assumes the parent-child relationship. I added the order-by attribute to the map, and it simply will not pay attention to it. Where, like any others, where the proposals in each request do not present a problem. Does anyone know why this can happen, and if it is anyway? I will be disappointed if I can not get around this.
You should study one of the LINQ providers for NHibernate. What you are looking for is a way to delay the loading of results for your query. LINQ's greatest power is that it does just that ... delays the results of your queries. When you actually create a query, it actually creates an expression tree that represents what you want to do so that it can be done at a later stage. Using the LINQ provider for NHibernate, you can do something like the following:
public abstract class Repository<T> where T: class { public abstract T GetByID(int id); public abstract IQueryable<T> GetAll(); public abstract T Insert(T entity); public abstract void Update(T entity); public abstract void Delete(T entity); } public class RecipientRepository: Repository<Recipient>; { // ... public override IQueryable<Recipient> GetAll() { using (ISession session = /* get session */) { // Gets a query that will return all Recipient entities if iterated IQueryable<Recipient> query = session.Linq<Recipient>(); return query; } } // ... } public class RecipientList { public IQueryable<Recipient> Recipients { RecipientRepository repository = new RecipientRepository(); return repository.GetAll(); // Returns a query, does not evaluate, so does not hit database } } // Consuming RecipientList in some higher level service, you can now do: public class RecipientService { public IList<Recipient> GetPagedList(int page, int size) { RecipientList list = // get instance of RecipientList IQueryable<Recipient> query = list.Recipients.Skip(page*size).Take(size); // Get your page IList<Recipient> listOfRecipients = query.ToList(); // <-- Evaluation happens here! reutrn listOfRecipients; } } With the code above (this is not a good example, but it demonstrates a general idea), you create an expression that represents what you want to do. Evaluation of this expression occurs only once ... and when the evaluation occurs, your database is requested with a specific query that will only return a specific subset of the rows that you actually requested. You donโt have to download all the records and then filter them out later on to the only page you requested ... no waste. If an exception occurs before you evaluate, for some reason you never end up in the database, all the more improving efficiency.
This power can go much further than requesting one page of results. Extension methods .Skip () and .Take () are available on all IQueryable <T> and IEnumerable <T> objects, as well as a whole bunch of others. In addition, you have .Where () ,. Except () ,. Join () and many, many others. This gives you the ability to, say, .GetAll (), and then filter the possible results of this query with one or more .Where () calls, ending with .Skip (...). Take (...), ending with a single evaluation in a call to .ToList () (or .ToArray ()).
This will require you to slightly change your domain and start passing IQueryable <T> or IEnumerable <T> around IList <T>, and only convert to IList <T> on your higher-level, "public" services.
If you are going to do something like this, I canโt think of a way, you can โwriteโ to the calculated collection for NH to persist. The selected collection will be read-only.
If this is normal, you can use this approach: http://www.acceptedeclectic.com/2007/12/generic-custom-nhibernate-collections.html
It wraps a PersistentGenericBag and adds some ekstra methods, just like you describe. GetPagedList () can then be implemented with criteria that return ReadOnlyCollection, as well as Count - returning a long course. As far as I know, the GetAll () method is optional, it will just return the collection to my knowledge.
What if this is a good idea, I think it is, if you have many collections where this is a real problem. If this were one or two cases, I would simply use a method for the entity that returned the collection on the pages.