gpt4 book ai didi

NHibernate:为什么 Linq First() 使用 FetchMany() 强制所有子集合和孙集合中的只有一项

转载 作者:行者123 更新时间:2023-12-02 22:53:18 27 4
gpt4 key购买 nike

领域模型

我有一个 Customer 的规范域,其中包含许多 Orders,每个 Order 都有许多 OrderItems >:

客户

public class Customer
{
public Customer()
{
Orders = new HashSet<Order>();
}
public virtual int Id {get;set;}
public virtual ICollection<Order> Orders {get;set;}
}

订单

public class Order
{
public Order()
{
Items = new HashSet<OrderItem>();
}
public virtual int Id {get;set;}
public virtual Customer Customer {get;set;}
}

订单商品

public class OrderItem
{
public virtual int Id {get;set;}
public virtual Order Order {get;set;}
}

问题

无论是使用 FluentNHibernate 还是 hbm 文件进行映射,我都会运行两个单独的查询,它们的 Fetch() 语法相同,但其中一个查询包含 .First() 扩展方法。

返回预期结果:

var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).ToList()[0];

仅返回每个集合中的单个项目:

var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).First();

我想我明白这里发生了什么,即 .First() 方法应用于前面的每个语句,而不仅仅是最初的 .Where() 子句。考虑到 First() 返回的是 Customer,这对我来说似乎是不正确的行为。

编辑2011-06-17

经过进一步的研究和思考,我相信根据我的映射,这个方法链有两个结果:

    .Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items);

注意:我认为我无法获得子选择行为,因为我没有使用 HQL。

  1. 当映射为 fetch="join" 时,我应该在 Customer、Order 和 OrderItem 表之间获得笛卡尔积。
  2. 当映射为 fetch="select" 时,我应该获取对 Customer 的查询,然后对 Orders 和 OrderItems 分别进行多个查询。

通过将 First() 方法添加到链中,我不知道应该发生什么。

get 发出的 SQL 查询是传统的左外连接查询,前面有 select top (@p0)

最佳答案

First() 方法被转换为 SQL(至少是 T-SQL),如 SELECT TOP 1 ...。与您的联接提取相结合,这将返回一行,其中包含一名客户、该客户的一份订单以及该订单的一项商品。您可能会认为这是 Linq2NHibernate 中的一个错误,但由于连接获取很少见(而且我认为您实际上会损害您的性能,因为在网络中拉动相同的客户和订单字段值作为每个项目行的一部分)我怀疑团队将修复它。

您想要的是一个客户,然后是该客户的所有订单以及所有这些订单的所有项目。这是通过让 NHibernate 运行 SQL 来实现的,该 SQL 将提取一条完整的客户记录(每个订单行对应一行)并构建客户对象图。将 Enumerable 转换为 List,然后获取第一个元素是可行的,但以下方法会稍微快一些:

var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items)
.AsEnumerable().First();

AsEnumerable() 函数强制评估由 Query 创建并用其他方法修改的 IQueryable,吐出内存中的 Enumerable,而不将其放入具体的列表中(如果愿意,NHibernate 可以简单地提取足够的信息)从 DataReader 中创建一个完整的顶级实例)。现在,First() 方法不再应用于要转换为 SQL 的 IQueryable,而是应用于对象图的内存中 Enumerable,在 NHibernate 完成其工作并给出您的Where子句之后,应该是零个或一个带有水合订单集合的客户记录。

就像我说的,我认为你使用连接获取会伤害自己。每行包含客户数据和订单数据,连接到每个不同的行。这是大量的冗余数据,我认为这会比 N+1 查询策略花费更多。

我能想到的处理此问题的最佳方法是每个对象执行一个查询来检索该对象的子对象。它看起来像这样:

var session = this.generator.Session;
var customer = session.Query<Customer>()
.Where(c => c.CustomerID == id).First();

customer.Orders = session.Query<Order>().Where(o=>o.CustomerID = id).ToList();

foreach(var order in customer.Orders)
order.Items = session.Query<Item>().Where(i=>i.OrderID = order.OrderID).ToList();

这需要对每个订单进行查询,再加上客户级别的两次查询,并且不会返回重复数据。这比返回包含客户和订单的每个字段以及每个项目的行的单个查询要好得多,也比发送每个项目的查询加上每个订单的查询加上客户的查询要好。

关于NHibernate:为什么 Linq First() 使用 FetchMany() 强制所有子集合和孙集合中的只有一项,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6380494/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com