NHibernate QueryOver Подзапрос - PullRequest
       17

NHibernate QueryOver Подзапрос

9 голосов
/ 15 февраля 2012

Я смотрел на подобные вопросы, но не могу найти простое объяснение. Я мог пропустить это, но обещаю, что посмотрел. На самом деле я даже не могу найти документацию, кроме одного поста в блоге, в котором все быстро затушевывается и предполагается, что вы знакомы с другими формами NH.

Учитывая, что многие ко многим между Program и Topic, где последний находится в иерархии Topics, я хочу получить все Programs для данного Topic, возможно, включая его подразделы. Поскольку программа может быть указана в нескольких подтемах данной родительской темы, мне нужно использовать подзапрос или иметь дело с необходимостью использовать разные (а простой подход TransformUsing(Transformers.DistinctRootEntity) не сработал).

Необработанный SQL должен выглядеть примерно так:

SELECT ProgramId, Title, bar, baz, foo FROM Programs 
WHERE ProgramId IN 
 (SELECT ProgramId from Program_Topics WHERE TopicId IN (1, 2, ...))

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

ProgramDTO pDTO = null;

/* topicIds is List<int> passed into function */

var query = Session.QueryOver<Program>()
.JoinQueryOver<Topic>(p => p.Topics)
.WhereRestrictionOn(pt => pt.Id).IsInG<int>(topicIds)     
.TransformUsing(Transformers.DistinctRootEntity)
.SelectList(list => list
        .Select(program => program.Id).WithAlias(() => pDTO.Id)
        .Select(program => program.Title).WithAlias(() => pDTO.Title)
        .Select(program => program.Location).WithAlias(() => pDTO.Location)
        .Select(program => program.Description).WithAlias(() => pDTO.Description)
)
.TransformUsing(Transformers.AliasToBean(typeof(ProgramDTO)));

return query.List<ProgramDTO>();    

Очевидно, что здесь выполняется объединение вместо подзапроса, но я не могу найти пример выполнения подзапроса со многими ко многим, как это.

public class Program : Entity {
    public virtual ISet<Topic> Topics { get; protected internal set; }
     ...
}

public class Topic : Entity {
    public virtual ISet<Program> Programs { get; protected internal set; }
    public virtual Topic ParentTopic { get; protected internal set; }
    ...
}

Ответы [ 2 ]

12 голосов
/ 15 февраля 2012

Необходимо создать подробный запрос, содержащий идентификаторы, а затем использовать этот подзапрос с основным запросом.

Я вставил здесь пример, поэтому вам нужно заменить соответствующие биты именами ваших классов и т. Д.

Сначала настройте (вы можете игнорировать этот бит): -

public class TestDto {
  public long Id { get; set; }
  public string Name { get; set; }
}
...
TestDto dto = null;
var ids = new List<int> { 1,2,5,7 };

Теперь подробный запрос: -

var idSubQuery = QueryOver.Of<CmsRegionContent>()
  .WhereRestrictionOn(w => w.Id).IsIn(ids)
  .Select(Projections.Distinct(Projections.Property<CmsPage>(s => s.Id)));

И последний момент - собрать все воедино: -

var query = Session.QueryOver<CmsPage>()
    .JoinQueryOver<CmsRegionContent>(l => l.CmsRegionContentList)
    .WithSubquery
    .WhereProperty(m => m.Id)
    .In(idSubQuery)
    .SelectList(list => list
                            .Select(p => p.Id).WithAlias(() => dto.Id)
                            .Select(p => p.PageName).WithAlias(() => dto.Name)
                )
                .TransformUsing(Transformers.AliasToBean(typeof(TestDto)));

var model = query.List<TestDto>();

Это создаст следующий SQL: -

SELECT
     this_.Id as y0_,
     this_.PageName as y1_ 
FROM cmspage this_ inner join cmsregioncontent cmsregionc1_ 
  on this_.Id=cmsregionc1_.PageId 
WHERE cmsregionc1_.Id in (
    SELECT
         distinct this_0_.Id as y0_ 
    FROM cmsregioncontent this_0_ 
    WHERE this_0_.Id in (
        1 /* ?p0 */,
         2 /* ?p1 */,
         5 /* ?p2 */,
         7 /* ?p3 */)
    )

Надеюсь, вы сможете следить за этим с вашими именами классов / свойств.

4 голосов
/ 15 февраля 2012

Ну, хэшируем это еще немного, и хотя мне не нравится одна часть результатов, она работает:

var distinctProgIdsSubQuery = QueryOver.Of<Program>().
JoinQueryOver<Topic>(p => p.Topics).
WhereRestrictionOn(pt => pt.Id).IsIn(topicIds)
.Select(Projections.Distinct(Projections.Property<Program>(p => p.Id)));


ProgramDTO pDTO = null;
var progQuery = Session.QueryOver<Program>()
    .WithSubquery.WhereProperty(p => p.Id).In(distinctProgIdsSubQuery)
    .SelectList(list => list
        .Select(program => program.Id).WithAlias(() => pDTO.Id)
        .Select(...)
        )
    .TransformUsing(Transformers.AliasToBean(typeof(ProgramDTO)));


return progQuery.List<ProgramDTO>();

Это производит

SELECT this_.ProgramId as y0_, ...
FROM Programs this_ 
WHERE this_.ProgramId in (
        SELECT distinct this_0_.ProgramId as y0_ 
        FROM
            Programs this_0_ 
        inner join
            Programs_Topics topics3_ 
                on this_0_.ProgramId=topics3_.ProgramId 
        inner join
            Topics topic1_ 
                on topics3_.TopicId=topic1_.TopicId 
        WHERE
            topic1_.TopicId in (
                @p1, @p2, ...
            )
    ) 

Это может быть ограничение NH, но не требуется для присоединения к таблице Programs в подзапросе. Я пытался написать это с другой стороны - то есть создать QueryOver.Of<Topic>(), но я не мог понять, как выбрать идентификаторы программ в конце - выбор только давал мне TopicIds, и даже тогда запрос все еще объединяет все три таблицы.

Я не уверен, что оптимизатор запросов MS-SQL избежит бесполезного объединения или нет, но было бы неплохо, если бы нам не пришлось полагаться на него.

Пока что это работает, и, надеюсь, у кого-то еще меньше болит голова, чем я, пытаясь это выяснить.

...