Подтвердить что ты не робот

Борьба с декартовым продуктом (x-join) при использовании NHibernate 3.0.0

Я плохо разбираюсь в математике, но я доволен тем, что декартовый продукт есть.
Вот моя ситуация (упрощенная):

public class Project{
 public IList<Partner> Partners{get;set;}
}
public class Partner{
 public IList<PartnerCosts> Costs{get;set;}
 public IList<Address> Addresses{get;set;}
}
public class PartnerCosts{
 public Money Total{get;set;}
}
public class Money{
 public decimal Amount{get;set;}
 public int CurrencyCode{get;set;}
}
public class Address{
 public string Street{get;set;}
}

Моя цель - эффективно загрузить весь проект.

Проблема, конечно, такова:

  • Если я пытаюсь найти партнеров по загрузке и их затраты, запрос возвращает gazillion rows
  • Если я ленивую загрузку Partner.Costs, db получает запрос спам (что немного быстрее, чем первый подход).

Как я читал, обычным обходным решением является использование MultiQueries, но я вроде как просто не понимаю.
Поэтому я надеюсь узнать на этом конкретном примере.

Как эффективно загрузить весь проект?

P.s. Я использую NHibernate 3.0.0.
Пожалуйста, не отправляйте ответы с hql или строковыми критериями api.

4b9b3361

Ответ 1

Хорошо, я написал пример для себя, отражающий вашу структуру, и это должно работать:

int projectId = 1; // replace that with the id you want
// required for the joins in QueryOver
Project pAlias = null;
Partner paAlias = null;
PartnerCosts pcAlias = null;
Address aAlias = null;
Money mAlias = null;

// Query to load the desired project and nothing else    
var projects = repo.Session.QueryOver<Project>(() => pAlias)
    .Where(p => p.Id == projectId)
    .Future<Project>();

// Query to load the Partners with the Costs (and the Money)
var partners = repo.Session.QueryOver<Partner>(() => paAlias)
    .JoinAlias(p => p.Project, () => pAlias)
    .Left.JoinAlias(() => paAlias.Costs, () => pcAlias)
    .JoinAlias(() => pcAlias.Money, () => mAlias)
    .Where(() => pAlias.Id == projectId)
    .Future<Partner>();

// Query to load the Partners with the Addresses
var partners2 = repo.Session.QueryOver<Partner>(() => paAlias)
    .JoinAlias(o => o.Project, () => pAlias)
    .Left.JoinAlias(() => paAlias.Addresses, () => aAlias)
    .Where(() => pAlias.Id == projectId)
    .Future<Partner>();

// when this is executed, the three queries are executed in one roundtrip
var list = projects.ToList();
Project project = list.FirstOrDefault();

Мои классы имели разные имена, но отражали ту же структуру. Я заменил имена, и я надеюсь, что нет опечаток.

Объяснение:

Алиасы необходимы для соединений. Я определил три запроса для загрузки Project, которые вы хотите, Partners со своими Costs и Partners с их Addresses. Используя .Futures(), я в основном говорю NHibernate, чтобы выполнить их в одном обратном направлении в тот момент, когда я действительно хочу получить результаты, используя projects.ToList().

Это приведет к появлению трех операторов SQL, которые действительно выполняются в одном обратном направлении. Три заявления вернут следующие результаты: 1) 1 строка с вашим проектом 2) x строк с Партнерами и их расходами (и деньгами), где x - общее количество затрат для партнеров по проекту 3) y строк с Партнерами и их Адресами, где y - общее количество Адресов для Партнеров проекта

Ваш db должен возвращать строки 1 + x + y вместо x * y строк, которые были бы декартовым произведением. Я надеюсь, что ваша БД действительно поддерживает эту функцию.

Ответ 2

Если вы используете Linq в своем NHibernate, вы можете упростить декартовое предупреждение с помощью этого:

int projectId = 1;
var p1 = sess.Query<Project>().Where(x => x.ProjectId == projectId);


p1.FetchMany(x => x.Partners).ToFuture();

sess.Query<Partner>()
.Where(x => x.Project.ProjectId == projectId)
.FetchMany(x => x.Costs)
    .ThenFetch(x => x.Total)
.ToFuture();

sess.Query<Partner>()
.Where(x => x.Project.ProjectId == projectId)
.FetchMany(x => x.Addresses)
.ToFuture();


Project p = p1.ToFuture().Single();

Подробное объяснение здесь: http://www.ienablemuch.com/2012/08/solving-nhibernate-thenfetchmany.html

Ответ 3

Вместо того, чтобы жаждать выборку нескольких коллекций и получить отвратительное декартово произведение:

Person expectedPerson = session.Query<Person>()
    .FetchMany(p => p.Phones)
        .ThenFetch(p => p.PhoneType)
    .FetchMany(p => p.Addresses)
    .Where(x => x.Id == person.Id)
    .ToList().First();

Вы должны размещать дочерние объекты в одном вызове базы данных:

// create the first query
var query = session.Query<Person>()
      .Where(x => x.Id == person.Id);
// batch the collections
query
   .FetchMany(x => x.Addresses)
   .ToFuture();
query
   .FetchMany(x => x.Phones)
   .ThenFetch(p => p.PhoneType)
   .ToFuture();
// execute the queries in one roundtrip
Person expectedPerson = query.ToFuture().ToList().First();

Я просто написал сообщение в блоге об этом, в котором объясняется, как избежать использования Linq, QueryOver или HQL http://blog.raffaeu.com/archive/2014/07/04/nhibernate-fetch-strategies.aspx

Ответ 4

Я просто хотел внести свой вклад в действительно полезный ответ Флориана. Я обнаружил трудный путь что ключом ко всему этому являются псевдонимы. Алиасы определяют, что входит в sql и используются как "идентификаторы" NHibernate. Минимальный Queryover для успешной загрузки трехуровневый граф объектов:

Project pAlias = null;
Partner paAlias = null;

IEnumerable<Project> x = session.QueryOver<Project>(() => pAlias)
 .Where(p => p.Id == projectId)
 .Left.JoinAlias(() => pAlias.Partners, () => paAlias)
 .Future<Project>();


session.QueryOver(() => paAlias).Fetch(partner => partner.Costs).
 .Where(partner => partner.Project.Id == projectId)
 .Future<Partner>();

Первый запрос загружает проект и его дочерние партнеры. Важной частью является псевдоним для Партнера. Алиас партнера используется для обозначения второго запроса. Второй запрос загружает партнеров и затраты. Когда это выполняется как "Multiquery", Nhibernate "знает", что первый и второй запрос связаны by paAlias ​​(или, скорее, сгенерированные sqls будут иметь псевдонимы столбцов, которые являются "идентичными" ). Таким образом, второй запрос продолжит загрузку партнеров, которые уже были запущены в первом запросе.