Реализация IPagedList <T>на моих моделях с использованием NHibernate - PullRequest
6 голосов
/ 18 мая 2009

Я обнаружил, что при использовании NHibernate и создании отношения «один ко многим» на объекте, когда множество становится очень большим, оно может значительно замедлиться. Теперь у меня есть методы в моем репозитории для сбора выгружаемого IList этого типа, однако я бы предпочел, чтобы эти методы также использовались в модели, потому что именно здесь другие разработчики сначала будут искать список дочерних объектов.

, например

RecipientList.Recipients возвратит каждого получателя в списке.

Я хотел бы реализовать способ добавления подкачки на всех моих страницах ко многим отношениям в моих моделях, используя предпочтительно интерфейс, но на самом деле все, что не навязывает типизированные отношения к модели. Например, было бы неплохо иметь следующий интерфейс:

public interface IPagedList<T> : IList<T>
{
    int Count { get; }
    IList<T> GetPagedList(int pageNo, int pageSize);
    IList<T> GetAll();
}

Тогда можно использовать его в коде ...

IList<Recipient> recipients = RecipientList.Recipients.GetPagedList(1, 400);

Я пытался придумать способы сделать это, не давая модели осознания того, что пейджинг, но сейчас я бьюсь головой о кирпичную стену.

Могу ли я в любом случае реализовать интерфейс аналогично тому, как это делает NHibernate для IList и lazyloading в настоящее время? У меня недостаточно знаний о NHibernate, чтобы знать.

Реализация этой идеи - даже хорошая идея? Ваши мысли будут оценены как единственный разработчик .NET в моем доме, у которого нет никого, кто мог бы отрицать идеи.

UPDATE

Пост ниже указал мне на атрибут custom-collection NHibernate, который будет работать хорошо. Однако я не уверен, что лучший способ обойти это, я пытался унаследовать от PersistentGenericBag, чтобы он имел те же основные функции IList без особой работы, однако я не уверен, как собрать список объектов на основе ISessionImplementor , Мне нужно знать как:

  • Получите какие-то подробности ICriteria для текущего IList, который я собираюсь заполнить
  • Получите детали отображения для конкретного свойства, связанного с IList, чтобы я мог создать свою собственную ICriteria.

Однако я не уверен, смогу ли я сделать что-либо из перечисленного?

Спасибо

Ответы [ 3 ]

2 голосов
/ 22 мая 2009

Хорошо, я опубликую это как ответ, потому что он делает в основном то, что хотел. Однако я хотел бы получить некоторую обратную связь, а также, возможно, ответ на мое одно предостережение о решении:

Я создал интерфейс под названием IPagedList.

public interface IPagedList<T> : IList<T>, ICollection
{

    IList<T> GetPagedList(int pageNo, int pageSize);

}

Затем создал базовый класс, который он наследует от 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;
        }
    }
}

Затем я создаю IUserCollectionType для NHibernate для использования в качестве настраиваемого типа коллекции и NHPagedList, который наследуется от PersistentGenericBag, а IPagedList - как самой фактической коллекции. Я создал два отдельных класса для них, потому что казалось, что использование IUserCollectionType никак не повлияло на фактическую коллекцию, которая будет использоваться, поэтому я оставил эти две части логики раздельными. Код ниже для обоих вышеперечисленных:

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 следующий:

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;
        }
    }

}

Вы заметите, что он проверит, была ли коллекция инициализирована или нет, чтобы мы знали, когда проверять базу данных на предмет списка страниц, или когда просто использовать ток в объектах памяти.

Теперь все готово, просто измените текущие ссылки IList на своих моделях на IPagedList, а затем сопоставьте NHibernate с новой настраиваемой коллекцией, используя беглый NHibernate ниже, и вы готовы к работе.

.CollectionType<PagedListFactory<Recipient>>()

Это первое исправление этого кода, поэтому для его совершенствования потребуется некоторый рефакторинг и модификации.

Моя единственная проблема на данный момент состоит в том, что он не получит элементы с постраничным распределением в том порядке, который файл сопоставления предлагает для отношения родитель-потомок. Я добавил на карту атрибут order-by, и он просто не будет обращать на него внимания. Где, как и в любом другом месте, где предложения в каждом запросе не проблема. У кого-нибудь есть идеи, почему это может происходить и есть ли что-нибудь вокруг этого? Я буду разочарован, если не смогу обойтись без этого.

2 голосов
/ 26 мая 2009

Вам следует обратиться к одному из поставщиков LINQ для NHibernate. То, что вы ищете, - это способ отсрочить загрузку результатов для вашего запроса. Самая большая сила LINQ в том, что он делает именно это ... с задержкой загружает результаты ваших запросов. Когда вы на самом деле строите запрос, на самом деле создается дерево выражений, которое представляет то, что вы хотите сделать, так что это действительно может быть сделано позже. Используя поставщика LINQ для NHibernate, вы сможете сделать что-то вроде следующего:

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;
    }
}

С помощью приведенного выше кода (это не очень хороший пример, но он демонстрирует общую идею) вы создаете выражение, представляющее то, что вы хотите сделать. Оценка этого выражения происходит только один раз ... и когда оценка происходит, ваша база данных запрашивается с помощью специального запроса, который будет возвращать только определенное подмножество строк, которые вы фактически запрашивали. Не нужно загружать все записи, а затем отфильтровывать их до одной страницы, которую вы запросили ... без потерь. Если перед оценкой возникает исключение, по какой-либо причине вы даже не попадаете в базу данных, еще больше повышая эффективность.

Эта сила может пойти гораздо дальше, чем запрос одной страницы результатов. Методы расширения .Skip () и .Take () доступны для всех объектов IQueryable и IEnumerable , а также для целого ряда других. Кроме того, у вас есть .Where (), .Except (), .Join () и многие, многие другие. Это дает вам возможность, скажем, .GetAll (), а затем отфильтровать возможные результаты этого запроса с одним или несколькими вызовами .Where (), заканчивая .Skip (...). Take (...) , заканчивающийся одной оценкой при вашем вызове .ToList () (или .ToArray ()).

Это потребует, чтобы вы несколько изменили свой домен и начали передавать IQueryable или IEnumerable вместо IList , и конвертировать в IList только на своем более высоком уровне, ' публичные услуги.

0 голосов
/ 18 мая 2009

Если вы собираетесь сделать что-то подобное, я не могу придумать, как вы могли бы «написать» в выгружаемую коллекцию, чтобы NH сохранился. Постраничная коллекция будет доступна только для чтения.

Если это нормально, тогда вы можете использовать такой подход: http://www.acceptedeclectic.com/2007/12/generic-custom-nhibernate-collections.html

Он упаковывает PersistentGenericBag и добавляет некоторые методы ekstra, как вы описали. Затем можно реализовать GetPagedList () с критериями, которые возвращают коллекцию ReadOnlyCollection, как и Count - возвращая, конечно, long. Метод GetAll () не будет необходимым, он будет просто возвращать саму коллекцию, насколько я вижу.

А если это хорошая идея, я думаю, что если у вас много коллекций, то это актуальная проблема. Если это всего лишь одно или два сопоставления, я бы просто использовал метод для самой сущности, который возвращал коллекцию в страницах.

...