6

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

例如

RecipientList.Recipients 将返回列表中的每个收件人。

我想实现一种方法,将我的所有oen 上的分页添加到我的模型中的许多关系中,最好使用一个接口,但实际上是任何不会将类型化关系强加到模型上的东西。例如,最好有以下界面:

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 的 custom-collection 属性,它可以很好地工作。但是我不确定解决此问题的最佳方法是什么,我尝试从 PersistentGenericBag 继承,以便它具有与 IList 相同的基本功能而无需太多工作,但是我不确定如何收集基于 ISessionImplementor 的对象列表. 我需要知道如何:

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

但是我不确定我是否可以做上述任何一个?

谢谢

4

3 回答 3

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 以用作自定义集合类型,并将继承自 PersistentGenericBag 的 NHPagedList、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 属性,它只是不会注意它。与每个查询中的任何其他 where 子句一样,没有问题。有谁知道为什么会发生这种情况以及周围是否存在?如果我不能解决这个问题,我会很失望。

于 2009-05-22T11:40:51.790 回答
2

您应该查看 NHibernate 的 LINQ 提供程序之一。您正在寻找的是一种延迟加载查询结果的方法。LINQ 的最大威力在于它确实做到了……延迟加载查询的结果。当您实际构建一个查询时,实际上它会创建一个表示您想要做的事情的表达式树,以便它可以在以后实际完成。通过使用 NHibernate 的 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;
    }
}

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

这种能力可以比查询单页结果更进一步。扩展方法 .Skip() 和 .Take() 可用于所有 IQueryable<T> 和 IEnumerable<T> 对象,以及一大堆其他对象。此外,您还有 .Where()、.Except()、.Join() 等等。这使您能够使用 .GetAll(),然后通过一次或多次调用 .Where() 过滤该查询的可能结果,最后以 .Skip(...).Take(...) ,在您的 .ToList() (或 .ToArray()) 调用中以单次评估结束。

这将要求您稍微更改您的域,并开始传递 IQueryable<T> 或 IEnumerable<T> 来代替 IList<T>,并且只转换为更高级别的 IList<T>,“面向公众”服务。

于 2009-05-26T05:42:44.243 回答
0

如果你要做这样的事情,我想不出办法,你可以“写”到分页集合中让 NH 坚持下去。分页集合将是只读的。

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

他正在包装 PersistentGenericBag 并添加一些 ekstra 方法,就像你描述的那样。然后可以使用返回 ReadOnlyCollection 的标准来实现 GetPagedList(),就像 Count 一样 - 当然返回 long。GetAll() 方法不是必需的,据我所知,它只会返回它自己的集合。

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

于 2009-05-18T18:24:58.517 回答