NHibernate QueryOver.List导致N + 1

时间:2013-05-12 15:28:48

标签: nhibernate fluent-nhibernate fluent-nhibernate-mapping

在NHibernate中有一个看似很棒的N + 1选择问题。我正在执行一个查询,我要求一堆实体,其中一个链接属性为null。在这种情况下,我实际上并不需要NHibernate返回链接属性,因为它只是为了选择正确的数据。

First Entity是预订窗口

public class BookingWindow : Entity<BookingWindow>
{
    // Blah blah blah

    /// <summary>
    /// Gets or sets the booking order item.
    /// </summary>
    /// <value>
    /// The booking order item.
    /// </value>
    public virtual BookingWindowOrderItem BookingOrderItem { get; set; }
}

BookingWindowOrderItem如下

public class BookingWindowOrderItem : OrderItem
{
    // Blah blah blah

    public virtual BookingWindow BookingWindow { get; set; }
}

以下是各自的映射

   public BookingWindowMap()
    {
        this.Schema("Customer");
        this.Table("BookingWindows");
        this.Id(x => x.Id).GeneratedBy.Guid();
        this.Component(x => x.WindowPeriod, m =>
        {
            m.Map(x => x.Min, "StartTime");
            m.Map(x => x.Max, "EndTime");
        });

        this.References(window => window.BookingOrderItem).PropertyRef("BookingWindow").Column("Id").LazyLoad().Nullable().ReadOnly();
        this.Map(x => x.Price);
        this.References(x => x.CustomerRoom).ForeignKey("RoomId").Column("RoomId");
    }

    public BookingWindowOrderItemMap()
    {
        this.DiscriminatorValue(1);
        this.References(x => x.BookingWindow).LazyLoad().Column("OrderItemForeignId").ForeignKey("OrderItemForeignId");
    }

现在当我执行以下查询时,我会找回没有订单商品的正确预订窗口。

Session.QueryOver<BookingWindow>().Where(w => w.CustomerRoom.Id == Guid.Parse(roomId)).Left.JoinQueryOver(bw => bw.BookingOrderItem).WhereRestrictionOn(item => item.Id).IsNull.List<BookingWindow>();

所以第一个查询会像这样被发送到数据库(选择了订单项列,这有点烦人,但真正的问题只需一分钟)

选择this_.Id为Id2_1_,this_.Price为Price2_1_,this_.RoomId为RoomId2_1_,this_.StartTime为StartTime2_1_,this_.EndTime为EndTime2_1_,bookingwin1_.Id为Id4_0_,bookingwin1_.Price为Price4_0_,bookingwin1_.Description为Descript4_4_0_,bookingwin1_.OrderId为OrderId4_0_,bookingwin1_.OrderItemParentId为OrderIte6_4_0_,bookingwin1_.OrderItemForeignId为OrderIte7_4_0_ FROM Customer.BookingWindows this_ left outer join Payment.OrderItem bookingwin1_ on this_.Id = bookingwin1_.OrderItemForeignId and bookingwin1_.OrderItemTypeId ='1'WHER this_。 RoomId =?和bookingwin1_.Id为空

但是,对于返回的每个预订窗口,即使我没有要求或需要,也会对链接的订单项目进行额外选择。这发生在查询方法中,所以我没有手动迭代返回的预订窗口。

选择bookingwin0_.Id如Id4_0_,bookingwin0_.Price如Price4_0_,bookingwin0_.Description如Descript4_4_0_,bookingwin0_.OrderId如OrderId4_0_,bookingwin0_.OrderItemParentId如OrderIte6_4_0_,bookingwin0_.OrderItemForeignId如OrderIte7_4_0_ FROM Payment.OrderItem bookingwin0_ WHERE bookingwin0_.OrderItemForeignId =?和bookingwin0_.OrderItemTypeId ='1'

任何人都可以向我解释我在这里犯的错误。也许它显而易见但我已经挣扎了几个小时并且在耐心结束时:)

1 个答案:

答案 0 :(得分:0)

我在您的映射中看到一个奇怪的部分:使用References作为一对一的映射样式。也许这是有目的的,但这导致了你的问题。

首先,正如文件所述[参考/多对一] [1]

  

参考文献用于在两者之间创建多对一关系   实体,并应用于“多方”。你引用了一个   单个其他实体,因此您使用References方法。 #有很多 /   一对多是References关系的“另一面”,和   得到应用于“一方面”。

换句话说,在BookingWindowOrderItemMap的表格中存储了对BookingWindow的引用。它可能意味着(通过数据库设计),可能有更多的OrderItem记录,引用相同的BookingWindow。但也许这就是你想要的,你在其他地方检查“唯一性”。我试图理解你的问题的次数越多,我就会投票给在BookingWindow 中的列中移动对OrderItem的引用

问题显示:

解决您的问题。当NHibernate收到BookingWindow列表时,下一步是构建代理。在此过程中,所有valueType / string属性都已设置,并且对于引用...对于引用,NHibernate会尝试准备延迟加载。

简化版本是,为每个属性BookingWindowOrderItem BookingOrderItem注入一个 promise ,用于BookingWindowOrderItem的实例,在首次触摸时返回。在标准情况下,当使用映射References时,NHibernate此时已经从BookingWindow的表ReferenceId加载。

在您的情况下,此ReferenceID由虚拟的,只读“当前项目ID”表示。绝对存在的ID ...但参考!我们只选择了BookingWindow s,它有NULL而不是引用。

但我们确实有 NOT NULL 参考ID(由当前实例ID表示)。

我们使用了.Left.JoinQueryOver。所以NHibernate确定,它已经在第一个查询中加载了所有数据......但是很困惑,因为在他的会话中没有订单ID等于BookingWindow.ID/ReferenceId

这就是原因(为什么它试图修复它......并且再次加载它)

所以这就是答案,为什么NHibernate做了“奇怪的选择”。不是建议如何解决它;)它可能是另一个问题和答案......