【问题标题】:Linq to NHibernate - eager load grandchildren but not childrenLinq to NHibernate - 急切地加载孙子而不是孩子
【发布时间】:2012-02-13 17:07:38
【问题描述】:

我正在使用 NHibernate 3.2 与 FluentNHibernate 和 Linq to NHibernate。我想使用 Linq to NHibernate 来急切地加载集合的所有孙子,而不必加载孩子。例如,假设我有以下课程:

public class Parent
{
    public virtual int Id { get; set; }
    public virtual IList<Child> Children { get; set; }
}

public class ParentMap : ClassMap<Parent>
{
    Id(x => x.Id);
    HasManyToMany(x => x.Children).ExtraLazyLoad();
}

public class Child
{
    public virtual int Id { get; set; }
    public virtual IList<Parent> Parents { get; set; }
    public virtual IList<Grandchild> Grandchildren { get; set; }
    public virtual ProhibitivelyLargeType ProhibitivelyLargeField { get; set; }
    public virtual ProhibitivelyLargeType RarelyUsedLargeField { get; set; }
}

public class ChildMap : ClassMap<Child>
{
    Id(x => x.Id);
    HasManyToMany(x => x.Parents).ExtraLazyLoad();
    HasManyToMany(x => x.Grandchildren).ExtraLazyLoad();
    Map(x => x.ProhibitivelyLargeField);
    Map(x => x.RarelyUsedField).LazyLoad();
}

public class Grandchild
{
    public virtual int Id { get; set; }
    public virtual IList<Child> Children { get; set; }
    public virtual int Age { get; set; }
}

public class GrandchildMap : ClassMap<Grandchild>
{
    Id(x => x.Id);
    HasManyToMany(x => x.Children).ExtraLazyLoad();
    Map(x => x.Age);
}

对于每个父母,我想找出该父母所有孙子女的总年龄。我可以使用以下方法:

Dictionary<Parent, int> grandchildAges = session.Query<Parent>()
    .FetchMany(p => p.Children)
    .ThenFetchMany(c => c.Grandchildren)
    .AsEnumerable()
    .ToDictionary(
        p => p,
        p => p.Children.SelectMany(c => c.Grandchildren).Sum(g => g.Age)
    );

此方法产生正确的结果。但是,它需要加载所有子对象。 Child 包含一个 ProhibitivelyLargeType 类型的字段,它不是延迟加载的,所以我真的不希望加载任何关于 Child 的内容,只加载它的 ID。但是,如果我不使用 FetchMany/ThenFetchMany,那么我就会遇到 N + 1 问题,并且需要为每个 Child 和 Grandchild 访问数据库,这也是不可接受的。

或者,我可以制作 ProhibitivelyLargeField LazyLoad。但是,大多数使用 Child 类的应用程序都需要使用 ProhibitivelyLargeField,但又不想加载 RarelyUsedLargeField,这已经是 LazyLoad。据我了解,加载一个 LazyLoad 属性会导致所有这些属性都被加载,因此此解决方案会使正常用例陷入困境。

有没有办法使用 Linq to NHibernate 获取我正在寻找的信息,还是必须使用 Criteria Query API?

谢谢!

编辑以举例说明为什么制作 ProhibitivelyLargeField LazyLoad 可能是不可取的

【问题讨论】:

  • 是否将ProhibitivelyLargeType 定义为延迟加载选项?
  • @Firo 不,ProhibitivelyLargeField 不能被延迟加载。
  • 因为...?我还没有看到一个禁用延迟加载的有效示例。
  • @Diego 我的理解是加载任何 LazyLoad 属性会导致加载所有 LazyLoad 属性,这对于 Child 类的普通消费者来说可能是不希望的。问题已更新以澄清。

标签: c# nhibernate linq-to-nhibernate eager-loading grandchild


【解决方案1】:

以下是QueryOver。它只是为了展示以两个较小的步骤加载结果的想法。也许你可以把它翻译成LINQ

// inititialize the dictionary
Grandchild grandchild = null;
Dictionary<Parent, int> dict = session.QueryOver<Parent>()
    .JoinQueryOver(p => p.Childs)
    .JoinAlias(c => c.GrandChilds, () => grandchild)
    .Select(Projections.Group<Parent>(p => p.Id), Projections.Sum(() => grandchild.Age))
    .AsEnumerable()
    .Cast<object[]>()
    .ToDictionary(
        array => session.Load<Parent>(array[0]),
        array => (int)array[1]
    );

// initialize all Parent proxies
session.QueryOver<Patient>()
    .WhereProperty(p => p.Id).In(dict.Keys.Select(p => p.Id))
    .ToList();

【讨论】:

  • 谢谢。您提供的代码与我现在的操作方式相似。 “将其翻译成 LINQ”部分是我一直无法解开的。
【解决方案2】:

我没有使用过 nhibernate,但我使用过 linq 到实体,据我所见,您正在执行大量的数据库查询。您应该改为执行单行查询,该查询仅返回您想要的数据:

from parent in session.Parents
let children = parent.Children
select new {parent = parent, children.SelectMany(c => c.Grandchildren).Sum(gc => gc.Age)}

抱歉,如果我有什么问题。好久没学C#了,我在玩手机。

如果这种方法不起作用,请告诉我,我会删除它。

【讨论】:

  • 谢谢,但如果我正确理解 Linq to NHibernate,那么像您提供的那种直接实现将为每个孩子和每个孙子访问数据库一次。 (FetchMany/ThenFetchMany 部分是强制它一次性获取所有内容。)
  • 那会很奇怪,你测试过吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-19
  • 2011-03-12
  • 1970-01-01
相关资源
最近更新 更多