2012-02-14 25 views
7

He visto las preguntas similares, pero no puedo encontrar una explicación simple. Podría haberlo perdido, pero prometo que miré. En realidad, ni siquiera puedo encontrar la documentación que no sea una sola publicación de blog que pasa por alto todo rápidamente y supone que está familiarizado con otras formas de NH.NHibernate QueryOver Subconsulta

Dada una relación muchos-a-muchos entre Program y Topic, cuando éste se encuentra en una jerarquía de Topics, quiero recuperar toda la Programs para un determinado Topic, posiblemente incluyendo sus subtemas. Como un programa puede enumerarse en múltiples subtemas de un tema principal dado, necesito usar una subconsulta o tratar con tener que usar distinct (y el enfoque simple de TransformUsing(Transformers.DistinctRootEntity) no funcionó).

Raw SQL debe ser algo como

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

Los resultados se echaron en un tipo de modelo para la transferencia a la vista. Mi primer intento fue la siguiente:

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>();  

Obviamente esto va en una unión en lugar de una subconsulta, pero no puedo encontrar un ejemplo de hacer una subconsulta con una relación muchos-a-muchos como este.

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; } 
    ... 
} 

Respuesta

4

Bueno, en este hash un poco más, y si bien no me gusta una parte de los resultados, que hace el trabajo:

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>(); 

Esto produce

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, ... 
      ) 
    ) 

esto puede ser una limitación de NH, pero no hay necesita para unirse a la tabla Programas en la subconsulta. Intenté escribir esto desde la otra dirección, es decir, crear un QueryOver.Of<Topic>(), pero no pude encontrar la manera de seleccionar las ID del programa al final: seleccionar solo me daba los TopicIds, e incluso entonces la consulta era todavía uniendo las tres tablas.

No estoy seguro de si el optimizador de consultas de MS-SQL evitará la unión inútil o no, pero sería bueno si no tuviéramos que confiar en ella.

Por ahora, sin embargo, esto funciona, y afortunadamente alguien más tiene menos dolores de cabeza que yo tratando de resolver esto.

+1

Esto realmente NO es la respuesta ya que Todavía creo la (s) unión (es) que quería evitar ... Elegí ir con una consulta HQL escrita a mano donde el problema se resuelve fácilmente. – Oliver

+1

Si escribe a mano una consulta HQL, abandona el tipado fuerte y la refactorización, lo que es un gran beneficio para QueryOver. Si bien crea una JOIN adicional, espero que un optimizador de consultas SQL vea que no necesita la tabla adicional leyendo la cláusula JOIN y averiguando que puede seleccionar ese campo y obtener los mismos resultados. Si nada más, es probable que ejecute el WHERE primero y luego unirse en un índice, por lo que será rápido. –

+0

Debo admitir que mi conocimiento de la ejecución del código SQL por SQL Server y sus posibles y probables optimizaciones es escaso, por lo que prefiero no confiar demasiado en él. Pero gracias por la información :-) – Oliver

10

Es necesario crear una consulta dettached que contiene el ID de y luego utilizar esta consulta sub con la consulta principal.

He pegado un ejemplo aquí, así que tendrá que reemplazar los bits correspondientes con sus nombres de clase etc.

En primer lugar la puesta a punto (se puede ignorar este bit): -

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 }; 

Ahora la consulta dettached: -

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

Y el bit final es poner todo junto: -

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>(); 

Esto creará el siguiente 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 */) 
    ) 

Esperamos que pueda ser capaz de seguir esto con sus nombres de clase/propiedad.

+0

Pude adaptar esto, pero observe que esto aún se une a la tabla de relaciones, por lo tanto, devuelve varias filas por entidad base. Compare 'SELECT * FROM cmspage WHERE id IN (SELECT ... cmsPageId FROM cmsregioncontent WHERE regionid IN (...))' a lo que tiene allí. La versión que se da aquí no es muy diferente a solo hacer una unión interna. –

+1

En realidad, después de verificar, NH todavía está generando Programas INNER JOIN ProgramsTopics INNER JOIN Topics. 'var topicSubQuery = QueryOver.Of () .WhereRestrictionOn (pt => pt.Id) .IsIn (topicIds) .Select (Projections.Distinct (Projections.Property (p => p.Id)));' 'var progQuery = Session.QueryOver () .Where (p => p.Status! = ProgramStatus.Archived) .JoinQueryOver (p => p.Topics) .WithSubquery.WhereProperty (pt => pt.Id). En (topicSubQuery) .SelectList (lista => lista/* ... snip ... * /) \t \t \t .TransformUsing (Transformers.AliasToBean (typeof (ProgramDTO))); ' –

Cuestiones relacionadas