2010-12-31 16 views
7

Tengo este gráfico de objetos:LINQ to NHibernate ThenFetch múltiples propiedades

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

Ahora estoy haciendo una consulta a dónde voy a devolver una lista de A s y necesito todos sus B s , C s, X sy Y s. También voy a agruparlos por B en una búsqueda.

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

Un par de cosas a tener en cuenta:

  1. Este es un informe donde todos los datos tiene que ser devuelto - Resultados sin límites no es un problema.
  2. Estoy haciendo la agrupación usando ToLookup porque el uso de group by parece ser más complicado cuando necesita todos los valores reales, necesita consultar la base de datos para los grupos y luego sus valores reales.

Mi pregunta es cómo especificar la estrategia de búsqueda correctamente. La forma en que he hecho es la única manera que encontré para esto para ejecutar (con exagerado todo el BX y por los valores) - pero produce SQL que parece equivocada:

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 

Como se puede ver que se está haciendo el valor de a.b 3 veces - b1 y b3 para la recuperación, y b6 para la cláusula where.

  1. Supongo que esto tiene un impacto negativo en el rendimiento de la base de datos. ¿Estoy en lo correcto?
  2. ¿Hay alguna forma de modificar mis llamadas .Fetch para que solo obtenga a.b una vez?
  3. ¿Este es un buen enfoque para mi problema?

Respuesta

4

Si realiza varias recuperaciones de propiedades de uno a muchos en una consulta, obtiene un producto cartesiano. NHibernate no maneja esto: AFAIK, se hizo deliberadamente para que se comportara como una combinación real de SQL. HQL hace lo mismo.

No necesita hacer todas las recuperaciones de una vez. Divida la consulta y ejecute cada búsqueda/combinación de uno a muchos en una consulta separada. Cada uno almacenará en caché sus datos en la sesión y conectará correctamente todas las referencias de objeto. (Nota: Nunca he intentado esto con LINQ, pero funciona en HQL, y el principio es el mismo)

De la parte superior de mi cabeza, podría ser algo como esto:

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

Hay Otra optimización que podría hacer es utilizar el método ToFuture() en lugar de ToList() ... No estoy seguro de cómo funciona con los métodos LINQ y ToLookup, pero no debería ser demasiado difícil hacerlo bien. ToFuture() pondrá en cola las consultas y las ejecutará todas a la vez en lugar de hacer conexiones de bases de datos separadas para cada una.

+1

En primer lugar, estos son todos muchos como se puede ver en el mapeo (solo hay un B en una A, y no una lista de ellos), por lo que toda la consulta solo cargará 4 entidades. En segundo lugar, lo que está sugiriendo es exactamente lo contrario de lo que yo quisiera: en lugar de reducir la complejidad de la consulta, la ha aumentado dividiéndola en varias consultas. La consulta ideal tendrá las siguientes combinaciones: 'desde [A] unión externa izquierda [B] en ...left outer join [X] en ... left outer join [Y] on ... '. – configurator

+1

Mi comentario anterior suena duro; No quise disuadirte de tratar de ayudar. Agradezco tu esfuerzo, incluso si no parece ser el resultado de mi comentario anterior. – configurator

+1

He comprobado el comportamiento sugerido sobre la precarga de entidades relacionadas. Utilizando NHibernate Profiler puedo ver que solo se obtienen una vez, por lo que puede confirmar que lo que dice bdrajer es cierto sobre el almacenamiento en caché de los datos en la sesión. –