Linq to NHibernate ThenFetch несколько свойств - PullRequest
7 голосов
/ 01 января 2011

У меня есть этот объект графа:

// 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 с, X с и Y с. Я также собираюсь сгруппировать их по 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 кажется более сложным, когда вам нужны все фактические значения - вам нужно запросить базу данных для групп, а затем для их фактических значений.

У меня вопрос, как правильно указать стратегию получения. То, как я это сделал, - единственный способ, с помощью которого я смог его запустить (получив все значения b.x и b.y), но он выдает 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

Как вы можете видеть, он получает значение a.b 3 раза - b1 и b3 для выборки и b6 для предложения where.

  1. Полагаю, это отрицательно влияет на производительность БД - я прав?
  2. Есть ли способ изменить мои .Fetch звонки, чтобы он только a.b получал один раз?
  3. Это хороший подход к моей проблеме?

1 Ответ

5 голосов
/ 07 января 2011

Если вы делаете несколько выборок свойств «один ко многим» в одном запросе, вы получаете декартово произведение.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 () поставит запросы в очередь и выполнит их все сразу, вместо того, чтобы делать отдельные подключения к базе данных для каждого.

...