在使用NHibernate 3.0.0时,战斗笛卡尔积(x-join)
我在math上不好,但是我想知道笛卡儿的产品是什么。
这是我的情况(简化):
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;} }
我的目标是有效地加载整个项目。
问题当然是:
- 如果我试图加载合作伙伴和他们的成本,查询返回gazillion行
- 如果我懒加载Partner.Costs,数据库获取请求垃圾邮件(这是比第一种方法快一点)
在我看来,常见的解决方法是使用MultiQueries,但我只是不明白。
所以我希望通过这个确切的例子来学习。
如何有效地加载整个项目?
Ps我正在使用NHibernate 3.0.0。
请不要用hql或stringbuild立的标准api方法发布答案。
好吧,我为自己写了一个反映你的结构的例子,这应该是有效的:
int projectId = 1; // replace that with the id you want // required for the joins in QueryOver Project pAlias = null; Partner paAlias = null; PartnerCosts pcAlias = null; Address aAlias = null; Money mAlias = null; // Query to load the desired project and nothing else var projects = repo.Session.QueryOver<Project>(() => pAlias) .Where(p => p.Id == projectId) .Future<Project>(); // Query to load the Partners with the Costs (and the Money) var partners = repo.Session.QueryOver<Partner>(() => paAlias) .JoinAlias(p => p.Project, () => pAlias) .Left.JoinAlias(() => paAlias.Costs, () => pcAlias) .JoinAlias(() => pcAlias.Money, () => mAlias) .Where(() => pAlias.Id == projectId) .Future<Partner>(); // Query to load the Partners with the Addresses var partners2 = repo.Session.QueryOver<Partner>(() => paAlias) .JoinAlias(o => o.Project, () => pAlias) .Left.JoinAlias(() => paAlias.Addresses, () => aAlias) .Where(() => pAlias.Id == projectId) .Future<Partner>(); // when this is executed, the three queries are executed in one roundtrip var list = projects.ToList(); Project project = list.FirstOrDefault();
我的class有不同的名字,但反映了完全相同的结构。 我replace了名字,我希望没有错别字。
说明:
别名是连接所必需的。 我定义了三个查询来加载你想要的Project
, Partners
的Costs
和Partners
的Addresses
。 通过使用.Futures()
我基本上告诉NHibernate执行他们在一个往返当我真的想要的结果,使用projects.ToList()
。
这将导致三个SQL语句确实在一个往返中执行。 这三个陈述将返回以下结果:1)您的项目2)x行与合作伙伴和他们的成本(和金钱),其中x是项目的合作伙伴的总成本3)行与行合作伙伴及其地址,其中y是项目合作伙伴的地址总数
你的数据库应该返回1 + x + y行,而不是x * y行,这将是一个笛卡尔积。 我希望你的数据库实际上支持这个function。
如果你在你的NHibernate上使用Linq,你可以用下面的方法简化笛卡儿的预防:
int projectId = 1; var p1 = sess.Query<Project>().Where(x => x.ProjectId == projectId); p1.FetchMany(x => x.Partners).ToFuture(); sess.Query<Partner>() .Where(x => x.Project.ProjectId == projectId) .FetchMany(x => x.Costs) .ThenFetch(x => x.Total) .ToFuture(); sess.Query<Partner>() .Where(x => x.Project.ProjectId == projectId) .FetchMany(x => x.Addresses) .ToFuture(); Project p = p1.ToFuture().Single();
详细的解释在这里: http : //www.ienablemuch.com/2012/08/solving-nhibernate-thenfetchmany.html
而不是急于获取多个集合,并得到一个讨厌的笛卡尔产品:
Person expectedPerson = session.Query<Person>() .FetchMany(p => p.Phones) .ThenFetch(p => p.PhoneType) .FetchMany(p => p.Addresses) .Where(x => x.Id == person.Id) .ToList().First();
您应该在一个数据库调用中批量处理子对象:
// create the first query var query = session.Query<Person>() .Where(x => x.Id == person.Id); // batch the collections query .FetchMany(x => x.Addresses) .ToFuture(); query .FetchMany(x => x.Phones) .ThenFetch(p => p.PhoneType) .ToFuture(); // execute the queries in one roundtrip Person expectedPerson = query.ToFuture().ToList().First();
我刚刚写了一篇关于它的博客文章,解释了如何避免使用Linq,QueryOver或HQL http://blog.raffaeu.com/archive/2014/07/04/nhibernate-fetch-strategies.aspx
我只是想为弗洛里安真正有用的答案作出贡献。 我发现困难的方式,所有这一切的关键是别名。 别名决定了sql的内容,并被NHibernate用作“标识符”。 成功加载三级对象图的最小查询是这样的:
Project pAlias = null; Partner paAlias = null; IEnumerable<Project> x = session.QueryOver<Project>(() => pAlias) .Where(p => p.Id == projectId) .Left.JoinAlias(() => pAlias.Partners, () => paAlias) .Future<Project>(); session.QueryOver(() => paAlias).Fetch(partner => partner.Costs). .Where(partner => partner.Project.Id == projectId) .Future<Partner>();
第一个查询加载项目及其子合作伙伴。 重要的部分是Partner的别名。 伙伴别名用于命名第二个查询。 第二个查询加载合作伙伴和成本。 当它作为“Multiquery”执行时,Nhibernate将“知道”第一个和第二个查询通过paAlias连接(或者说生成的sqls将具有“相同”的列别名)。 因此,第二个查询将继续加载已经在第一个查询中启动的合作伙伴。