Свободный выпуск NHibernate N + 1 со сложными объектами

У меня проблема с NHibernate, которая запрашивает базу данных слишком много раз. Я просто понял, что это, вероятно, относится к проблеме n + 1, но я не могу понять, как изменить мои сопоставления для решения проблемы.

Как вы увидите, мои попытки включают указание не на ленивую загрузку других объектов, но, похоже, это не трюк.

Это запрос:

public IQueryable<Report> ReadAll(DateTime since)
{
    return m_session.QueryOver<Report>()
       .JoinQueryOver(r => r.Mail)
       .Where(m => m.Received >= since)
       .List()
       .AsQueryable();
}

Заранее благодарим за любой ответ! Если вам нужна дополнительная информация о моих объектах или сопоставлениях, сообщите мне.

Упрощенный граф объектов (некоторые опущены):

public class Report : EntityBase
{
    public virtual Product Product { get; set; }
    public virtual StackTrace StackTrace { get; set; }
    public virtual Mail Mail { get; set; }

    public virtual IList<ClientUser> ReadBy { get; set; }
}

-

public class Product : EntityBase
{
    public virtual string Name { get; set; }
    public virtual Version Version { get; set; }
    public virtual IList<Report> Reports { get; set; }
    public virtual IList<StackTrace> StackTraces { get; set; }
}

-

public class StackTrace : EntityBase
{
    public virtual IList<StackTraceEntry> Entries { get; set; }
    public virtual IList<Report> Reports { get; set; }
    public virtual Product Product { get; set; }
}

Примеры сопоставления:

public class ReportMap : ClassMap<Report>
{
    public ReportMap()
    {
        Table("Report");

        References(x => x.User)
          .Column("EndUserId")
          .Not.LazyLoad();

        References(x => x.Product)
          .Column("ProductId")
          .Not.LazyLoad();

        References(x => x.StackTrace)
          .Column("StackTraceId")
          .Not.LazyLoad();

        HasManyToMany(x => x.ReadBy)
          .Cascade.SaveUpdate()
          .Table("ClientUserRead")
          .ParentKeyColumn("ReportId")
          .ChildKeyColumn("ClientUserId")
          .Not.LazyLoad().BatchSize(200);
    }
}

-

public class StackTraceMap : ClassMap<StackTrace>
{
    public StackTraceMap()
    {
        Table("StackTrace");

        References(x => x.Product)
          .Column("ProductId");

        HasMany(x => x.Entries)
          .KeyColumn("StackTraceId")
          .Not.LazyLoad()
          .Cascade
          .All().BatchSize(500);

        HasMany(x => x.Reports)
          .KeyColumn("StackTraceId")
          .Inverse().BatchSize(100);
    }
}

Ответ 1

Путь к использованию заключается в использовании пакетной выборки. Подробнее об этом читайте здесь:

Как избавить ассоциации нагрузки без дублирования в NHibernate?

В каждом сопоставлении сущности применяется BatchSize (для отношения many-to-one - избегать 1 + N)

public ReportMap()
{
   Table(...)
   BatchSize(25);
   ...

И на каждой коллекции (решает проблему с one-to-many 1 + N)

HasMany(x => x.Reports)
      .KeyColumn("StackTraceId")
      .BatchSize(25)
      ...

Ответ 2

В запросе можно указать пути извлечения. Например, этот путь выборки может рассказать, что запрос с нетерпением присоединяется к продукту и основным объектам, а также к воображаемой ассоциации в коллекции клиентов:

public IQueryable<Report> ReadAll(DateTime since)
{
    return m_session.QueryOver<Report>()
       .JoinQueryOver(r => r.Mail)
       .Where(m => m.Received >= since)
       .Fetch(x => x.Product).Eager
       .Fetch(x => x.Mail).Eager
       .Fetch(x => x.ReadBy.First().SomeProp).Eager
       .List()
       .AsQueryable();
}

Если вы хотите, чтобы это всегда происходило, попробуйте использовать .Fetch.Join() вместо .Not.LazyLoad() для ассоциаций. Но я бы не рекомендовал это, потому что это может привести к тому, что простые запросы станут огромными. Также могут помочь пакетные или подзапросы.