Листание ленивой коллекции с помощью NHibernate

Я прочитал эту статью где Айенде утверждает, что NHibernate может (по сравнению с EF 4):

  • Коллекция с lazy = ”extra” - Lazy extra означает, что NHibernate адаптируется к операциям, которые вы можете запускать поверх ваших коллекций. Это означает, что blog.Posts.Count не будет принудительно загружать всю коллекцию, а скорее создаст оператор «select count (*) from Posts where BlogId = 1», и этот blog.Posts.Contains () будет аналогичным результатом в одном запросе вместо того, чтобы платить за загрузку всей коллекции в память.
  • Фильтры коллекций и страничные коллекции - это позволяет вам определять дополнительные фильтры (включая разбиение по страницам!) Поверх ваших коллекций сущностей, что означает, что вы можете легко постранично просматривать коллекцию blog.Posts и вам не нужно загружать все это в память.

Поэтому я решил собрать тестовый пример. Я создал клише-модель блога как простую демонстрацию с двумя следующими классами:

public class Blog
{
    public virtual int Id { get; private set;  }
    public virtual string Name { get; set; }

    public virtual ICollection<Post> Posts { get; private set;  }

    public virtual void AddPost(Post item)
    {
        if (Posts == null) Posts = new List<Post>();
        if (!Posts.Contains(item)) Posts.Add(item);
    }
}

public class Post
{
    public virtual int Id { get; private set; }
    public virtual string Title { get; set; }
    public virtual string Body { get; set; }
    public virtual Blog Blog { get; private set; }
}

Мои файлы сопоставлений выглядят так:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
  <class xmlns="urn:nhibernate-mapping-2.2" name="Model.Blog, TestEntityFramework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="Blogs">
    <id name="Id" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Id" />
      <generator class="identity" />
    </id>
    <property name="Name" type="System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Name" />
    </property>
    <property name="Type" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Type" />
    </property>
    <bag lazy="extra" name="Posts">
      <key>
        <column name="Blog_Id" />
      </key>
      <one-to-many class="Model.Post, TestEntityFramework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </bag>
  </class>
</hibernate-mapping>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
  <class xmlns="urn:nhibernate-mapping-2.2" name="Model.Post, TestEntityFramework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="Posts">
    <id name="Id" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Id" />
      <generator class="identity" />
    </id>
    <property name="Title" type="System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Title" />
    </property>
    <property name="Body" type="System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Body" />
    </property>
    <many-to-one class="Model.Blog, TestEntityFramework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="Blog">
      <column name="Blog_id" />
    </many-to-one>
  </class>
</hibernate-mapping>

Мой тестовый пример выглядит примерно так:

        using (ISession session = Configuration.Current.CreateSession()) // this class returns a custom ISession that represents either EF4 or NHibernate
        {
            blogs = (from b in session.Linq<Blog>()
                         where b.Name.Contains("Test")
                         orderby b.Id
                         select b);

            Console.WriteLine("# of Blogs containing 'Test': {0}", blogs.Count());
            Console.WriteLine("Viewing the first 5 matching Blogs.");

            foreach (Blog b in blogs.Skip(0).Take(5))
            {
                Console.WriteLine("Blog #{0} \"{1}\" has {2} Posts.", b.Id, b.Name, b.Posts.Count);
                Console.WriteLine("Viewing first 5 matching Posts.");

                foreach (Post p in b.Posts.Skip(0).Take(5))
                {
                    Console.WriteLine("Post #{0} \"{1}\" \"{2}\"", p.Id, p.Title, p.Body);
                }
            }
        }

Используя lazy = "extra", вызов b.Posts.Count делает SELECT COUNT(Id)..., и это здорово. Однако b.Posts.Skip(0).Take(5) просто захватывает все сообщения для Blog.Id =? Id, а затем LINQ на стороне приложения просто берет первые 5 из полученной коллекции.

Что дает?


person moribvndvs    schedule 12.03.2010    source источник
comment
+1 за хороший вопрос. Я уже некоторое время задавался вопросом о возможностях ленивой загрузки NHibernates, но у меня не было времени опробовать их.   -  person Spencer Ruport    schedule 12.03.2010
comment
возможно, часть LINQ to NHibernate, касающаяся фильтров сбора, не реализована   -  person Jaguar    schedule 12.03.2010
comment
Одно предостережение при использовании lazy = extra. Мы начали использовать это в нашем приложении, и все работало нормально, пока мы не развернули его на машине Sql Server 2000 (все еще одобренное клиентом программное обеспечение). Поскольку мы используем guid в качестве полей идентификации, мы начали получать исключение «Невозможно выполнить подсчет по уникальному идентификатору» (приблизительно). Я просмотрел исходный код NH и обнаружил, что это была дополнительная ленивая загрузка, которая выбирает первый ключевой столбец и выполняет «select count (‹columName›)», которое было нашим полем GUID.   -  person Rob Kent    schedule 22.07.2011


Ответы (1)


Я почти уверен (читая комментарии), что он говорит о CreateFilter ISession.

Вы можете выполнить разбиение на страницы следующим образом (из документов 13.13):

Collections are pageable by using the IQuery interface with a filter:

IQuery q = s.CreateFilter( collection, "" ); // the trivial filter
q.setMaxResults(PageSize); 
q.setFirstResult(PageSize * pageNumber); 
IList page = q.List();

Или (из документов 17.1.4):

s.CreateFilter( lazyCollection, "").SetFirstResult(0).SetMaxResults(10).List();

Это не так гладко, как использование методов System.Linq. Думаю, когда-нибудь они тоже присоединятся к синтаксису.

person asgerhallas    schedule 12.03.2010
comment
Хм, да. разочаровывающе негладкий. Но это сойдет. Спасибо. - person moribvndvs; 12.03.2010
comment
Привет, можно ли опубликовать исходный пример с реализованной техникой? Спасибо. - person UpTheCreek; 05.08.2010