我在 ASP.NET MVC 中创建一个网站并使用 NHibernate 作为 ORM。我的数据库中有以下表格:
- 书签
- 标签书签(联结表)
- 标签
映射:
public BookmarkMap()
{
Table("Bookmarks");
Id(x => x.Id).Column("Id").GeneratedBy.Identity();
Map(x => x.Title);
Map(x => x.Link);
Map(x => x.DateCreated);
Map(x => x.DateModified);
References(x => x.User, "UserId");
HasManyToMany(x => x.Tags).AsSet().Cascade.None().Table("TagsBookmarks").ParentKeyColumn("BookmarkId")
.ChildKeyColumn("TagId");
}
public TagMap()
{
Table("Tags");
Id(x => x.Id).Column("Id").GeneratedBy.Identity();
Map(x => x.Title);
Map(x => x.Description);
Map(x => x.DateCreated);
Map(x => x.DateModified);
References(x => x.User, "UserId");
HasManyToMany(x => x.Bookmarks).AsSet().Cascade.None().Inverse().Table("TagsBookmarks").ParentKeyColumn("TagId")
.ChildKeyColumn("BookmarkId");
}
我需要 Bookmarks 和 Tags 表中的数据。更具体地说:我需要 20 个带有相关标签的书签。我做的第一件事是从 Bookmarks 表中选择 20 个书签 ID。我这样做是因为分页不适用于我在第二个查询中获得的笛卡尔积。
第一次查询:
IEnumerable<int> bookmarkIds = (from b in SessionFactory.GetCurrentSession().Query<Bookmark>()
where b.User.Username == username
orderby b.DateCreated descending
select b.Id).Skip((page - 1) * pageSize).Take(pageSize).ToList<int>();
之后,我为这些 ID 选择书签。
第二个查询:
IEnumerable<Bookmark> bookmarks = (from b in SessionFactory.GetCurrentSession().Query<Bookmark>().Fetch(t => t.Tags)
where b.User.Username == username && bookmarkIds.Contains(b.Id)
orderby b.DateCreated descending
select b);
我使用 fetch 的原因是因为我想避免 N+1 查询。这有效但会产生笛卡尔积。我在一些帖子中读到您应该避免笛卡尔积,但我真的不知道如何在我的情况下做到这一点。
我还阅读了有关为 N+1 查询设置批量大小的内容。这真的比这个单一查询更快吗?
一个用户最多可以为一个书签添加 5 个标签。我每页选择 20 个书签,因此第二个查询的最坏情况是:5 * 20 = 100 行。
当书签和标签表中有大量数据时,这会影响性能吗?我应该采取不同的做法吗?
最佳答案
这不是笛卡尔积。
~ Figure A ~
Bookmarks -> Tags -> Tag
笛卡尔积是两个不同集合的所有可能组合。例如,假设我们有三个表:Customer、CustomerAddress 和 CustomerEmail。客户有很多地址,他们也有很多电子邮件地址。
~ Figure B ~
Customers -> Addresses -> Emails
如果你写了一个查询...
select *
from
Customer c
left outer join CustomerAddress a
on c.Id = a.Customer_id
left outer join CustomerEmail e
on c.Id = e.Customer_id
where c.Id = 12345
... 而这个客户有 5 个地址和 5 个电子邮件地址,您最终会返回 5 * 5 = 25
行。您可以看到为什么这对性能不利。这是不必要的数据。了解客户地址和电子邮件地址的所有可能组合对我们没有任何用处。
对于您的查询,您不会返回任何不必要的行。结果集中的每一行都直接对应于您感兴趣的其中一个表中的一行,反之亦然。没有乘法。相反,您有 TagsBookmarksCount + BookmarksThatDontHaveTagsCount
。
查找笛卡尔积的关键位置是当您的查询分支为两个独立的不相关集合时。如果您只是越来越深入地挖掘单个子集合链,如 图 A 所示,则没有笛卡尔积。您的查询返回的行数将受到该最深集合返回的行数的限制。一旦你分支到一边,你现在在查询中有两个并行的、并排的集合,如 图 B 所示,那么你就有了笛卡尔积,结果将是不必要地成倍增加。
要修复笛卡尔积,请将查询拆分为多个查询,以便添加行数,而不是相乘。使用 NHibernate 的 Future
方法,您可以将这些单独的查询批处理在一起,因此您仍然只有一次到数据库的往返行程。参见 one of my other answers有关如何在 NHibernate 中修复笛卡尔积的示例。
关于c# - 这个 NHibernate 查询会影响性能吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19844256/