Свободный выпуск 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
Путь к использованию заключается в использовании пакетной выборки. Подробнее об этом читайте здесь:
В каждом сопоставлении сущности применяется 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()
для ассоциаций. Но я бы не рекомендовал это, потому что это может привести к тому, что простые запросы станут огромными. Также могут помочь пакетные или подзапросы.