32

我数学不好,但我有点明白什么是笛卡尔积
这是我的情况(简化):

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,db 会收到垃圾邮件请求(这比第一种方法快一点)

正如我所读到的,常见的解决方法是使用 MultiQueries,但我有点不明白。
所以我希望通过这个确切的例子来学习。

如何有效地加载整个项目?

Ps 我正在使用 NHibernate 3.0.0。
请不要使用 hql 或字符串形式的标准 api 方法发布答案。

4

4 回答 4

47

好的,我为自己写了一个例子来反映你的结构,这应该可以工作:

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

我的类有不同的名称,但反映了完全相同的结构。我替换了名字,希望没有错别字。

解释:

连接需要别名。我定义了三个查询来加载Project你想要的,Partnerswith theirCosts和 the Partnerswith their Addresses。通过使用,.Futures()我基本上告诉 NHibernate 在我真正想要结果的那一刻在一次往返中执行它们,使用projects.ToList().

这将导致三个 SQL 语句确实在一次往返中执行。这三个语句将返回以下结果:1) 1 行包含您的项目 2) x 行包含合作伙伴及其成本(和资金),其中 x 是项目合作伙伴的成本总数 3) y 行包含合作伙伴及其地址,其中 y 是项目合作伙伴的地址总数

您的数据库应该返回 1+x+y 行,而不是 x*y 行,这将是一个笛卡尔积。我确实希望您的数据库实际上支持该功能。

于 2011-03-12T21:50:58.590 回答
5

如果您在 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

于 2012-08-25T11:00:28.363 回答
2

而不是急切地获取多个集合并获得令人讨厌的笛卡尔积:

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/

于 2014-07-04T05:48:47.830 回答
1

我只是想为弗洛里安的真正有用的答案做出贡献。我发现所有这一切的关键是别名。别名决定了进入 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 连接(或者更确切地说,生成的 sql 将具有“相同”的列别名)。因此,第二个查询将继续加载已在第一个查询中启动的合作伙伴。

于 2014-02-07T12:25:22.393 回答