Linq to NHibernate thenFetch 多个属性

发布于 2024-10-10 01:52:50 字数 2187 浏览 8 评论 0原文

我已经得到了这个对象图:

// 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
}      

现在我正在进行一个查询,我将返回一个 A 列表,并且我需要它们的所有 B,<代码>C、XY。我还将按 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);
   }
}

有几点需要注意:

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

我的问题是如何正确指定获取策略。我这样做的方式是我发现运行它的唯一方式(已获取所有 bx 和 by 值) - 但它生成的 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

如您所见,它正在获取 ab 的值 3 次 - b1b3 用于获取,b6 用于 where 子句。

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

I've got this object graph:

// 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
}      

Now I'm making a query where I'm going to return a list of As and I need all their Bs, Cs, Xs and Ys. I'm also going to group them by B into a lookup.

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);
   }
}

A few things to note:

  1. This is a report where all data needs to be returned - unbounded results is not a problem.
  2. I'm doing the grouping by using ToLookup because using group by seems to be more complicated when you need all the actual values - you'd need to query the database for the groups and then for their actual values.

My question is how to specify the fetching strategy properly. The way I've done it is the only way I found for this to run (having fetched all of the b.x and b.y values) - but it produces SQL which seems wrong:

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

As you can see it's getting the value for a.b 3 times - b1 and b3 for the fetching, and b6 for the where clause.

  1. I assume this has a negative impact on DB performance - am I correct?
  2. Is there a way to modify my .Fetch calls so it only fetches a.b once?
  3. Is this a good approach to my problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

悲歌长辞 2024-10-17 01:52:50

如果您在一个查询中多次提取一对多属性,您将获得笛卡尔积。 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 命令执行,而不是为每个查询进行单独的数据库连接。

If you do multiple fetches of one-to-many properties in one query, you get a cartesian product. NHibernate doesn't handle this - AFAIK, it was done deliberately to make it behave like a real SQL join. HQL does the same thing.

You don't need to do all fetches in one go. Split the query and execute each one-to-many fetch/join in a separate query. Each will cache its data in the session and connect all the object references properly. (Note: I never tried this with LINQ, but it does work in HQL, and the principle is the same)

Off the top of my head, it could look something like this:

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);
}

There is one further optimization you could do, use ToFuture() method instead of ToList()... I'm not sure how it works with LINQ and ToLookup methods, but it shouldn't be too hard to get right. ToFuture() will queue the queries and execute them as one sql command instead of doing separate database connections for each one.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文