使用 NHibernate 3.0.0 时对抗笛卡尔积(x-join)
我数学不好,但我大概知道笛卡尔积是什么。
这是我的情况(简化):
public class Project{
public IList<Partner> Partners{get;set;}
}
public class Partner{
public IList<PartnerCosts> Costs{get;set;}
public IList<Address> Addresses{get;set;}
}
public class PartnerCosts{
public Money Total{get;set;}
}
public class Money{
public decimal Amount{get;set;}
public int CurrencyCode{get;set;}
}
public class Address{
public string Street{get;set;}
}
我的目标是有效加载整个项目。
当然问题是:
- 如果我尝试急切加载合作伙伴及其成本,查询会返回大量行
- 如果我延迟加载 Partner.Costs,数据库会收到垃圾邮件请求(这比第一种方法要快一点)
据我所知,常见的解决方法是使用 MultiQueries,但我有点不明白。
所以我希望通过这个例子来学习。
如何有效加载整个项目?
Ps我使用的是NHibernate 3.0.0。
请不要使用 hql 或字符串形式的标准 api 方法发布答案。
I'm bad at math but I kind get idea what cartesian product is.
Here is my situation (simplified):
public class Project{
public IList<Partner> Partners{get;set;}
}
public class Partner{
public IList<PartnerCosts> Costs{get;set;}
public IList<Address> Addresses{get;set;}
}
public class PartnerCosts{
public Money Total{get;set;}
}
public class Money{
public decimal Amount{get;set;}
public int CurrencyCode{get;set;}
}
public class Address{
public string Street{get;set;}
}
My aim is to effectively load entire Project.
Problem of course is:
- If I try to eager load partners and their costs, query returns gazillion rows
- If I lazy load Partner.Costs, db gets request spammed (which is a bit faster than first approach)
As I read, common workaround is to use MultiQueries, but I kind a just don't get it.
So I'm hoping to learn through this exact example.
How to effectively load whole Project?
P.s. I'm using NHibernate 3.0.0.
Please, do not post answers with hql or string fashioned criteria api approaches.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
好的,我为自己写了一个示例,反映了您的结构,这应该可行:
我的类有不同的名称,但反映了完全相同的结构。我替换了名字,希望没有错别字。
说明:
连接需要别名。我定义了三个查询来加载您想要的
Project
、Partners
及其Costs
和Partners
及其 <代码>地址。通过使用.Futures()
,我基本上告诉 NHibernate 在我真正想要结果时使用projects.ToList()
在一次往返中执行它们。这将导致在一次往返中确实执行了 3 个 SQL 语句。这三个语句将返回以下结果:
1) 1 行包含您的项目
2) x 行包含合作伙伴及其成本(和资金),其中 x 是项目合作伙伴的成本总数
3) y 行包含合作伙伴及其地址,其中 y 是项目合作伙伴的地址总数
您的数据库应返回 1+x+y 行,而不是 x*y 行,这将是笛卡尔积。我确实希望您的数据库实际上支持该功能。
Ok, I wrote an example for myself reflecting your structure and this should work:
My classes had different names but reflected the exact same structure. I replaced the names and I hope there are no typos.
Explanation:
The aliases are required for the joins. I defined three queries to load the
Project
you want, thePartners
with theirCosts
and thePartners
with theirAddresses
. By using the.Futures()
I basically tell NHibernate to execute them in one roundtrip at the moment when I actually want the results, usingprojects.ToList()
.This will result in three SQL statements that are indeed executed in one roundtrip. The three statements will return the following results:
1) 1 row with your Project
2) x rows with the Partners and their Costs (and the Money), where x is the total number of Costs for the Project's Partners
3) y rows with the Partners and their Addresses, where y is the total number of Addresses for the Project's Partners
Your db should return 1+x+y rows, instead of x*y rows, which would be a cartesian product. I do hope that your DB actually supports that feature.
如果您在 NHibernate 上使用 Linq,则可以通过以下方式简化笛卡尔预防:
此处详细说明:http://www.ienablemuch.com/2012/08/solving-nhibernate-thenfetchmany.html
If you are using Linq on your NHibernate, you can simplify cartesian-prevention with this:
Detailed explanation here: http://www.ienablemuch.com/2012/08/solving-nhibernate-thenfetchmany.html
不要急于获取多个集合并获得令人讨厌的笛卡尔积:
您应该在一个数据库调用中批量处理子对象:
我刚刚写了一篇关于它的博客文章,其中解释了如何使用 Linq、QueryOver 或 HQL 来避免这种情况
http://blog.raffaeu.com/archive/2014 /07/04/nhibernate-fetch-strategies/
Instead of eager fetch multiple collections and get a nasty Cartesian product:
You should batch children objects in one database call:
I just wrote a blog post about it which explains how to avoid that using Linq, QueryOver or HQL
http://blog.raffaeu.com/archive/2014/07/04/nhibernate-fetch-strategies/
我只是想为弗洛里安的真正有用的答案做出贡献。我发现了困难的方法
所有这一切的关键是别名。别名决定了 sql 中的内容
并被 NHibernate 用作“标识符”。成功加载的最小 Queryover
三级对象图是这样的:
第一个查询加载项目及其子伙伴。重要的部分是 Partner 的别名。
合作伙伴别名用于命名第二个查询。第二个查询加载合作伙伴和成本。
当它作为“多重查询”执行时,Nhibernate 将“知道”第一个和第二个查询是连接的
通过 paAlias (或者更确切地说,生成的 sql 将具有“相同”的列别名)。
因此,第二个查询将继续加载第一个查询中已启动的合作伙伴。
I just wanted to contribute to the really helpful answer by Florian. I found out the hard way
that the key to all of this is are the aliases. The aliases determines what goes in to the sql
and are used as "identifiers" by NHibernate. The minimal Queryover to successfully load a
three level object graph is this:
The first query loads project and its child partners. The important part is the alias for Partner.
The partner alias is used to name the second query. The second query loads partners and costs.
When this executed as a "Multiquery", Nhibernate will "know" that the first and second query are connected
by the paAlias (or rather the sqls generated will have column aliases that are "identical").
So the second query will continue the loading of Partners that was already started in the first query.