Linq to NHibernate ThenFetch 多个属性

Posted

技术标签:

【中文标题】Linq to NHibernate ThenFetch 多个属性【英文标题】:Linq to NHibernate ThenFetch multiple properties 【发布时间】:2011-06-02 03:20:51 【问题描述】:

我有这个对象图:

// Lots of stuff omitted for brevity; these are all virtual properties and there
// are other properties which aren't shown on all classes.
class A 
    B b;
    C c;
    DateTime timestamp;

class B 
    X x;
    Y y;

class X 
    int id;

class C  
class Y  

or to put it more simply,
a = 
   b: 
      x  id: int ,
      y:  
   ,
   c:  ,
   timestamp: DateTime
      

现在我正在查询我将返回As 的列表,我需要他们所有的Bs、Cs、Xs 和Ys。我还将按 B 将它们分组到查找中。

ILookup<B, A> GetData(List<int> ids) 
    using (ISession session = OpenSession()) 
        var query = from a in session.Query<A>()
                    where ids.Contains(a.b.x.id)
                    orderby A.timestamp descending
                    select a;

        query = query
            .Fetch(a => a.b)
            .ThenFetch(b => b.x)
            .Fetch(a => a.b)
            .ThenFetch(b => b.y)
            .Fetch(a => a.c);

       return query.ToLookup(a => a.b);
   

需要注意的几点:

    这是一个需要返回所有数据的报告 - 无限的结果不是问题。 我正在使用ToLookup 进行分组,因为当您需要所有实际值时,使用group by 似乎更复杂——您需要查询数据库中的组,然后查询它们的实际值。

我的问题是如何正确指定获取策略。我这样做的方式是我发现它运行的唯一方式(已获取所有 b.x 和 b.y 值) - 但它产生的 SQL 似乎是错误的:

select  /* snipped - every mapped field from a0, b1, x2, b3, y4, c5 - but not b6 */
from     [A] a0
         left outer join [B] b1
           on a0.B_id = b1.BId
         left outer join [X] x2
           on b1.X_id = x2.XId
         left outer join [B] b3
           on a0.B_id = b3.BId
         left outer join [Y] y4
           on b3.Y_id = y4.YId
         left outer join [C] c5
           on a0.C_id = c5.CId,
         [B] b6
where    a0.B_id = b6.BId
         and (b6.X_id in (1, 2, 3, 4, 5))
order by a0.timestamp desc

如您所见,它获得了 3 次 a.b 的值 - b1b3 用于获取,b6 用于 where 子句。

    我认为这会对数据库性能产生负面影响 - 我是否正确? 有没有办法修改我的.Fetch 调用,使其只获取一次a.b? 这是解决我的问题的好方法吗?

【问题讨论】:

【参考方案1】:

如果您在一个查询中多次获取一对多属性,您将获得笛卡尔积。 NHibernate 不处理这个 - AFAIK,它是故意让它表现得像一个真正的 SQL 连接。 HQL 做同样的事情。

您无需一次性完成所有提取。拆分查询并在单独的查询中执行每个一对多获取/连接。每个都将在会话中缓存其数据并正确连接所有对象引用。 (注:我从来没有用LINQ试过这个,但是在HQL中确实可以,原理是一样的)

在我的脑海中,它可能看起来像这样:

ILookup<B, A> GetData(List<int> ids) 
using (ISession session = OpenSession()) 
    var query = from a in session.Query<A>()
                where ids.Contains(a.b.x.id)
                orderby A.timestamp descending
                select a;

    query
        .Fetch(a => a.b)
        .ThenFetch(b => b.x)
        .ToList();
    query
        .Fetch(a => a.b)
        .ThenFetch(b => b.y)
        .Fetch(a => a.c)
        .ToList();

   return query.ToLookup(a => a.b);

您还可以做进一步的优化,使用 ToFuture() 方法而不是 ToList()...我不确定它如何与 LINQ 和 ToLookup 方法一起使用,但应该不难做到正确. ToFuture() 将对查询进行排队,并将它们作为一个 sql 命令执行,而不是为每个单独的数据库连接。

【讨论】:

首先,从映射中可以看出,这些都是多对一的(A中只有一个B,而不是它们的列表),所以整个查询只会加载 4 个实体。其次,您的建议与我想要的完全相反-您没有降低查询的复杂性,而是通过将其拆分为多个查询来增加它。理想的查询将具有以下连接:from [A] left outer join [B] on ... left outer join [X] on ... left outer join [Y] on .... 我之前的评论听起来很刺耳;我并不是要阻止你尝试提供帮助。我确实感谢您的努力,即使与我之前的评论看起来不一样。 我已经检查了有关预加载相关实体的建议行为。使用 NHibernate Profiler,我可以看到它们只被提取一次,因此可以确认 bdrajer 所说的关于在会话中缓存数据是正确的。

以上是关于Linq to NHibernate ThenFetch 多个属性的主要内容,如果未能解决你的问题,请参考以下文章

NHibernate 或 LINQ to SQL [关闭]

有利于启动; Linq to SQL 还是 Nhibernate?

Linq To Nhibernate 性能优化(入门级)

Linq to NHibernate ThenFetch 多个属性

不支持 linq to nhibernate compareto

Linq To NHibernate Plus sql 用户定义函数