优化NHibernate的SQL连接查询

3

假设有一个相对简单的领域模型,涉及订单、商品和发货,其中订单是根实体,发货是根实体。我想查找给定订单的所有发货。查询非常直接,但我在NHibernate中看到了不希望看到的行为。

模型

public class Order
{
    public Order(){ Items = new List<LineItem>(); }
    public virtual int Id { get; private set; }
    public virtual DateTime Created { get; set; }
    public virtual IList<LineItem> Items { get; private set; }
}

public class LineItem
{
    public virtual int Id { get; private set; }
    public virtual int Quantity { get; set; }
    public virtual Order Order { get; set; }
}

public class Shipment
{
    public virtual int Id { get; private set; }
    public virtual DateTime Date { get; set; }
    public virtual LineItem LineItem { get; set; }
}

LINQ

使用NHibernate.Linq进行此查询:

var shipments = from shipment in session.Linq<Shipment>()
                where shipment.LineItem.Order == order
                select shipment;

以下 SQL 查询的结果:

SELECT this_.Id            as Id5_2_,
       this_.Date          as Date5_2_,
       this_.LineItem_id   as LineItem3_5_2_,
       lineitem1_.Id       as Id4_0_,
       lineitem1_.Quantity as Quantity4_0_,
       lineitem1_.Order_id as Order3_4_0_,
       order2_.Id          as Id3_1_,
       order2_.Created     as Created3_1_,
       order2_.IsClosed    as IsClosed3_1_
FROM   [Shipment] this_
       left outer join [LineItem] lineitem1_
         on this_.LineItem_id = lineitem1_.Id
       left outer join [Order] order2_
         on lineitem1_.Order_id = order2_.Id
WHERE  lineitem1_.Order_id = 1 /* @p0 */

生成的Shipment对象是正确的,但查询加载了太多数据,因为我只对装运日期感兴趣。订单和行项目数据会立即被丢弃并且从未使用。我已经尝试使用延迟加载以及我能在网上找到的每种获取策略,但我无法让它简单地返回基本数据。
如何减少SQL查询中的噪声,以便仅加载装运数据和行项目的主键以支持延迟加载?类似于这样的内容:
SELECT this_.Id            as Id5_2_,
       this_.Date          as Date5_2_,
       this_.LineItem_id   as LineItem3_5_2_,
       lineitem1_.Id       as Id4_0_,
FROM   [Shipment] this_
       inner outer join [LineItem] lineitem1_
         on this_.LineItem_id = lineitem1_.Id
WHERE  lineitem1_.Order_id = 1 /* @p0 */

自定义SQL查询(更新)

使用类似下面的自定义SQL查询可以得到所需的性能和正确的行为。但这有点违背了ORM的初衷。为什么NHibernate不能生成如此简单的查询呢?

Session
    .CreateSQLQuery(
            @"SELECT SH.*, LI.Id FROM Shipment SH
              INNER JOIN LineItem LI ON LI.Id = SH.LineItem_id
              WHERE LI.Order_id = ?" )
    .SetInt32( 0, order.Id )
    .List<Shipment>();

你能发一下你的HBM文件吗? - Chris S
4个回答

1

如果你只对日期感兴趣,而不是以 "select shipment;" 结束你的 LINQ 语句,你可以以 "select shipment.Date;" 结束它,这样你就不会返回完整的对象层次结构。如果你想要一些额外的细节,你可以创建一个匿名类型?

var shipments = from shipment in session.Linq() where shipment.LineItem.Order == order select new {Id = shipment.Id, Date = shipment.Date, LineItemId = shipment.LineItem.Id, OrderId = shipment.LineItem.Order.Id};


哦,那个差不多可以。使用你的想法确实可以生成一个更简洁的查询(尽管它仍然比所需的联接更多)。然而 - 我确实需要真正的Shipment对象,因为实际模型有点更复杂。 - Paul Alexander

1
你可以在你的领域模型Order类中添加多对多关系。如果你正在使用nhibernate映射xml文件,可以通过将以下内容添加到Order映射来实现这一点。
<bag name="Shipments" table="LineItem" lazy="true">
    <key column="id"/>
    <many-to-many class="Shipment" column="lineitem_id" />
</bag>

你需要在你的Orders类中添加一个Shipments属性(应该是IList类型,否则你可能需要使用非泛型的IList接口)。

然后你可以在你的linq查询中查询Shipments属性,这样应该会得到更清晰的连接。

var shipments = from shipment in order.Shipments select shipment.Date;

这是一个显而易见的解决方案 - 但由于域原因无法实现。订单不负责发货。发货是在完全不同的流程和服务中发生的事件。 - Paul Alexander

1

你应该能够在C#中使用lambda表达式和新的对象初始化方式。

我已经有一段时间没有做过这样的事情了,因为我回到了老派的2.0。

我知道在linq中你可以这样做,但我不确定Nhibernate linq是否支持此功能。

var shipments = from shipment in session.Linq<Shipment>()
                                where shipment.LineItem.Order == order
                                select(x => new Shipment { Date = x.Date } );

看看这个链接在这里, 它是用c#编写的,但在一个Java网站上。这应该只执行一个语句,去选择订单等于指定订单的日期。但它将返回一个Shipment对象,只填充了日期。


这似乎是为了一些在其他ORM中非常简单的事情而做了很多工作。为什么我不能告诉NHibernate生成一个更好、更简单的查询呢? - Paul Alexander
我相信这是可能的,但我不完全确定如何做到这一点。以下三种方法是我开始尝试实现这个目标的地方。ISession.CreateCriteria ISession.CreateQuery ISession.CreateSqlQuery这是我所能做的最好的事情,希望它有所帮助。 - Peter

0

根据进一步的研究和这里的帖子,答案是你不能 :(


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接