使用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();