使用NHibernate 3.0.0时对抗笛卡尔积(x-join)
我数学不好,但我知道什么是数学。使用NHibernate 3.0.0时对抗笛卡尔积(x-join),nhibernate,eager-loading,cartesian-product,Nhibernate,Eager Loading,Cartesian Product,我数学不好,但我知道什么是数学。 以下是我的情况(简化): 公共类项目{ 公共IList伙伴{get;set;} } 公共课伙伴{ 公共IList成本{get;set;} 公共IList地址{get;set;} } 公共课成本{ 公共资金总额{get;set;} } 公款{ 公共十进制数{get;set;} 公共int CurrencyCode{get;set;} } 公共课堂演讲{ 公共字符串Street{get;set;} } 我的目标是有效地加载整个项目 问题当然是: 若我试图加载合作
以下是我的情况(简化):
公共类项目{
公共IList伙伴{get;set;}
}
公共课伙伴{
公共IList成本{get;set;}
公共IList地址{get;set;}
}
公共课成本{
公共资金总额{get;set;}
}
公款{
公共十进制数{get;set;}
公共int CurrencyCode{get;set;}
}
公共课堂演讲{
公共字符串Street{get;set;}
}
我的目标是有效地加载整个项目
问题当然是:
- 若我试图加载合作伙伴及其成本,查询将返回无数行
- 如果我延迟加载Partner.Costs,db会收到垃圾邮件请求(这比第一种方法快一点)
所以我希望通过这个确切的例子来学习 如何有效加载整个项目? 另外,我正在使用NHibernate 3.0.0。
请不要用hql或字符串形式的标准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();
int projectId=1;//用你想要的id替换它
//QueryOver中的联接需要
项目pAlias=null;
Partner paAlias=null;
PartnerCosts pcAlias=null;
地址aAlias=null;
金钱=零;
//查询以加载所需的项目,而不加载其他内容
var projects=repo.Session.QueryOver(()=>pAlias)
.Where(p=>p.Id==projectd)
.Future();
//查询以向合作伙伴加载成本(和资金)
var partners=repo.Session.QueryOver(()=>paAlias)
.JoinAlias(p=>p.Project,()=>pAlias)
.Left.JoinAlias(()=>paAlias.Costs,()=>pcAlias)
.JoinAlias(()=>pcAlias.Money,()=>mAlias)
.Where(()=>pAlias.Id==projectId)
.Future();
//查询以加载带有地址的合作伙伴
var partners2=repo.Session.QueryOver(()=>paAlias)
.JoinAlias(o=>o.Project,()=>pAlias)
.Left.JoinAlias(()=>paAlias.Addresses,()=>aAlias)
.Where(()=>pAlias.Id==projectId)
.Future();
//执行此操作时,三个查询将在一次往返中执行
var list=projects.ToList();
Project=list.FirstOrDefault();
我的类有不同的名称,但反映了完全相同的结构。我替换了名字,希望没有拼写错误
说明:
连接需要别名。我定义了三个查询来加载您想要的项目
,合作伙伴
及其成本
,合作伙伴
及其地址。通过使用.Futures()
我基本上告诉NHibernate在实际需要结果时,使用projects.ToList()
在一次往返中执行它们
这将导致在一次往返中执行三条SQL语句。这三条语句将返回以下结果:
1) 1行与您的项目
2) x行与合作伙伴及其成本(以及资金),其中x是项目合作伙伴的成本总数
3) 包含合作伙伴及其地址的y行,其中y是项目合作伙伴的地址总数
数据库应该返回1+x+y行,而不是x*y行,这将是笛卡尔积。我确实希望您的数据库能够真正支持该功能。如果您在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();
int projectd=1;
var p1=sess.Query(),其中(x=>x.ProjectId==ProjectId);
p1.FetchMany(x=>x.Partners).ToFuture();
sess.Query()
.Where(x=>x.Project.ProjectId==ProjectId)
.FetchMany(x=>x.Costs)
.ThenFetch(x=>x.Total)
.ToFuture();
sess.Query()
.Where(x=>x.Project.ProjectId==ProjectId)
.FetchMany(x=>x.Addresses)
.ToFuture();
项目p=p1.ToFuture().Single();
这里有详细的解释:我只是想对弗洛里安的回答做出贡献。我找到了艰难的道路
所有这些的关键是别名。别名决定了进入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>();
Project pAlias=null;
Partner paAlias=null;
IEnumerable x=session.QueryOver(()=>pAlias)
.Where(p=>p.Id==projectd)
.Left.JoinAlias(()=>pAlias.Partners,()=>paAlias)
.Future();
session.QueryOver(()=>paAlias.Fetch(partner=>partner.Costs)。
.Where(partner=>partner.Project.Id==projectd)
.Future();
第一个查询加载项目及其子伙伴。重要的部分是合作伙伴的别名。
合作伙伴别名用于命名第二个查询。第二个查询加载合作伙伴和成本。
当它作为“多查询”执行时,Nhibernate将“知道”第一个和第二个查询是连接的
paAlias(或者更确切地说,生成的sql将具有“相同”的列别名)。
因此,第二个查询将继续加载在第一个查询中已经启动的合作伙伴。而不是急切地获取多个集合并获取一个讨厌的笛卡尔乘积:
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();
Person expectedPerson=session.Query()
.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();
//创建第一个查询
var query=session.query()
。其中(x=>x.Id==person.Id);
//批处理集合
查询
.FetchMany(x=>x.Addresses)
.ToFuture();
查询
.FetchMany(x=>x.Phones)
.ThenFetch(p=>p.PhoneType)
.ToFuture();
//在一次往返中执行查询
Person expectedPerson=query.ToFuture().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();