使用NHibernate </t>在我的模型上实现IPagedList <t>

时间:2009-05-18 09:57:15

标签: c# asp.net nhibernate interface asp.net-3.5

我发现当使用NHibernate并在一个对象上创建一对多的关系时,当许多变得非常大时,它会显着减慢。现在我在我的存储库中有方法来收集该类型的分页IList,但是我更喜欢在模型上使用这些方法,因为这通常是其他开发人员首先要收集子对象列表的地方。

e.g。

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和延迟加载的方式实现接口吗?我对NHibernate知之甚少。

实施这个甚至是个好主意吗?您的想法将被认为是内部唯一的.NET开发人员,我没有人可以反复提出想法。

更新

下面的帖子向我指出了NHibernate的自定义集合属性,它可以很好地工作。但是我不确定最好的解决方法是什么,我试图从PersistentGenericBag继承,以便它具有与IList相同的基本功能而没有太多工作,但是我不确定如何基于ISessionImplementor收集对象列表。我需要知道如何:

  • 获取我将要填充的当前IList的某些ICriteria详细信息
  • 获取与IList关联的特定属性的映射详细信息,以便创建自己的ICriteria。

但我不确定我是否可以做以上任何一种情况?

由于

3 个答案:

答案 0 :(得分:2)

好的我会发布这个作为答案,因为它主要是按我的意愿行事。不过,我想要一些反馈,也可能是迄今为止我对解决方案的一个警告的答案:

我创建了一个名为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;
        }
    }
}
然后我创建一个用于NHibernate的IUserCollectionType作为自定义集合类型,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 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;
        }
    }

}

您会注意到它将检查集合是否已初始化,以便我们知道何时检查数据库中的分页列表或何时只使用当前的内存对象。

现在你准备好了,只需将模型上当前的IList引用更改为IPagedList,然后将NHibernate映射到新的自定义集合,使用流畅的NHibernate,如下所示,您就可以了。

.CollectionType<PagedListFactory<Recipient>>()

这是此代码的第一次尝试,因此需要进行一些重构和修改才能使其完美。

我目前唯一的问题是,它不会按照映射文件为父子关系建议的顺序获取分页项。我已经为地图添加了一个order-by属性,它只是不会注意它。每个查询中的任何其他where子句在哪里都没有问题。有没有人知道为什么会发生这种情况,如果还有它呢?如果我不能解决这个问题,我会感到失望。

答案 1 :(得分:2)

您应该查看NHibernate的LINQ提供程序之一。您正在寻找的是一种延迟加载查询结果的方法。 LINQ的最大优势在于它确实可以...延迟加载查询结果。当您实际构建查询时,实际上它创建了一个表达您想要执行的操作的表达式树,以便它可以在以后完成。通过使用NHIDnate的LINQ提供程序,您可以执行以下操作:

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

使用上面的代码(它不是一个很好的例子,但它确实展示了一般的想法),你构建了一个表达你想要做的事情的表达式。对该表达式的评估只发生一次......当评估发生时,将使用特定查询查询您的数据库,该查询仅返回您实际请求的特定行的子集。无需加载所有记录,然后将其过滤到您请求的单个页面...没有浪费。如果在您评估之前发生异常,无论出于何种原因,您甚至都不会访问数据库,从而进一步提高效率。

此功能可以比查询单页结果更远。所有IQueryable&lt; T&gt;都可以使用扩展方法.Skip()和.Take()。和IEnumerable&lt; T&gt;对象,以及其他一大堆。另外,你有.Where(),. Except(),.Join()等等。这为你提供了权力,例如.GetAll(),然后通过对.Where()的一次或多次调用来过滤该查询的可能结果,以.Skip(...)结束。取(...) ,以.ToList()(或.ToArray())调用的单一评估结束。

这需要您稍微更改域名,并开始传递IQueryable&lt; T&gt;或IEnumerable&lt; T&gt;周围代替IList&lt; T&gt;,并且仅转换为IList&lt; T&gt;。在您的更高级别,“面向公众”的服务。

答案 2 :(得分:0)

如果你要做那样的事情,我想不出办法,你就可以“写”到NH的分页集合中继续存在。分页收藏将是只读的。

如果可以,那么你可以使用这样的方法:http://www.acceptedeclectic.com/2007/12/generic-custom-nhibernate-collections.html

他正在包装PersistentGenericBag并添加一些ekstra方法,就像你描述的那样。 GetPagedList()然后可以使用一个标准来实现,该标准返回一个ReadOnlyCollection,可以计数 - 返回一个长的过程。 GetAll()方法不是必需的,就我所见,它只是自己返回集合。

至于这是一个好主意,我认为,如果你有很多收藏品,这是一个实际问题。如果它只是一两次碰撞,我会选择在自己的实体上有一个方法,它会在页面中返回集合。