使用 NHibernate 对延迟加载的集合进行分页
我阅读了这篇文章其中 Ayende 指出 NHibernate 可以(与 EF 4 相比):
- 带有lazy=”extra”的集合 – Lazy extra 意味着 NHibernate 适应 您可能运行的操作 您收藏的顶部。这意味着 blog.Posts.Count 不会强制 整个集合的负载,但是 而是会创建一个“选择计数(*) 来自 BlogId = 1 的帖子” 声明,并且 blog.Posts.Contains() 同样会 结果是单个查询而不是 付出装载整个的代价 收集到内存。
- 集合过滤器和分页集合 - 这使您可以 定义附加过滤器(包括 分页!)在您的实体之上 集合,这意味着您可以 轻松浏览博客。帖子 集合,而不必加载 整个事情都进入记忆。
所以我决定整理一个测试用例。我创建了陈词滥调的博客模型作为一个简单的演示,其中有两个类,如下所示:
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",调用 < code>b.Posts.Count 确实执行了 SELECT COUNT(Id)...
这很棒。但是,b.Posts.Skip(0).Take(5)
只是抓取 Blog.Id = ?id 的所有帖子,然后应用程序端的 LINQ 只是从结果中取出前 5 个帖子收藏。
什么给?
I read this article where Ayende states NHibernate can (compared to EF 4):
- Collection with lazy=”extra” – Lazy extra means that NHibernate adapts to
the operations that you might run on
top of your collections. That means
that blog.Posts.Count will not force a
load of the entire collection, but
rather would create a “select count(*)
from Posts where BlogId = 1”
statement, and that
blog.Posts.Contains() will likewise
result in a single query rather than
paying the price of loading the entire
collection to memory.- Collection filters and paged collections - this allows you to
define additional filters (including
paging!) on top of your entities
collections, which means that you can
easily page through the blog.Posts
collection, and not have to load the
entire thing into memory.
So I decided to put together a test case. I created the cliché Blog model as a simple demonstration, with two classes as follows:
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; }
}
My mappings files look like this:
<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>
My test case looks something like this:
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);
}
}
}
Using lazy="extra", the call to b.Posts.Count
does do a SELECT COUNT(Id)...
which is great. However, b.Posts.Skip(0).Take(5)
just grabs all Posts for Blog.Id = ?id, and then LINQ on the application side is just taking the first 5 from the resulting collection.
What gives?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我很确定(阅读评论)他正在谈论 ISession 的 CreateFilter。
您可以像这样进行分页(来自文档 13.13):
或者(来自文档 17.1.4):
这并不像使用 System.Linq 方法那么顺利。我想他们有时也会加入语法。
I'm pretty sure (reading the comments) that he's talking about the CreateFilter of ISession.
You can do paging like this (from the docs 13.13):
Or (from the docs 17.1.4):
That's is not as smooth as using the System.Linq methods. I guess they'll join the syntax some time too.