2008-12-02 12 views
19

Tengo una jerarquía de entidades de 3 niveles: Customer-Order-Line, que me gustaría recuperar en su totalidad para un cliente determinado, utilizando ISession.Get (id). Tengo los siguientes fragmentos XML:NHibernate Eager Obtención de varios niveles

customer.hbm.xml:

<bag name="Orders" cascade="all-delete-orphan" inverse="false" fetch="join"> 
    <key column="CustomerID" /> 
    <one-to-many class="Order" /> 
</bag> 

order.hbm.xml:

<bag name="Lines" cascade="all-delete-orphan" inverse="false" fetch="join"> 
    <key column="OrderID" /> 
    <one-to-many class="Line" /> 
</bag> 

He utilizado el fetch = "unirse" para indicar que atribuir Quiero buscar las entidades hijo para cada padre, y esto ha construido el SQL correcto:

SELECT 
    customer0_.ID AS ID8_2_, 
    customer0_.Name AS Name8_2_, 
    orders1_.CustomerID AS CustomerID__4_, 
    orders1_.ID AS ID4_, 
    orders1_.ID AS ID9_0_, 
    orders1_.PostalAddress AS PostalAd2_9_0_, 
    orders1_.OrderDate AS OrderDate9_0_, 
    lines2_.OrderID AS OrderID__5_, 
    lines2_.ID AS ID5_, 
    lines2_.ID AS ID10_1_, 
    lines2_.[LineNo] AS column2_10_1_, 
    lines2_.Quantity AS Quantity10_1_, 
    lines2_.ProductID AS ProductID10_1_ 

FROM Customer customer0_ 

LEFT JOIN [Order] orders1_ 
     ON customer0_.ID=orders1_.CustomerID 

LEFT JOIN Line lines2_ 
     ON orders1_.ID=lines2_.OrderID 

WHERE customer0_.ID=1 

Hasta ahora, esto parece bueno - SQL devuelve el conjunto correcto de registros (con solo un orderid distinto), pero cuando ejecuto una prueba para confirmar el número correcto de entidades (desde NH) para pedidos y líneas, obtengo los resultados incorrectos

I debería obtener (de mis datos de prueba), 1xOrder y 4xLine, sin embargo, estoy obteniendo 4xOrder y 4xLine. Parece que NH no reconoce el grupo de "repetición" de información de orden en el conjunto de resultados, ni "reutiliza" correctamente la entidad de orden.

Estoy usando todos los enteros ID (PK), y he intentado implementar IComparable de T y IEquatable de T con este ID, con la esperanza de que NH verá la igualdad de estas entidades. También he intentado descartar Equals y GetHashCode para usar la ID. Ninguno de estos 'intentos' ha tenido éxito.

¿La operación de recuperación de niveles múltiples es compatible con NH y, en caso afirmativo, hay una configuración de XML necesaria (o algún otro mecanismo) para admitirla?


NB: He utilizado la solución de sirocco con algunos cambios en mi propio código para finalmente resolver este. el xml debe cambiarse de una bolsa a otra, para todas las colecciones, y las entidades se cambiaron para implementar IComparable <>, que es un requisito de establecer un conjunto único.

public class BaseEntity : IComparable<BaseEntity> 
{ 
    ... 

    private Guid _internalID { get; set; } 
    public virtual Guid ID { get; set; } 

    public BaseEntity() 
    { 
     _internalID = Guid.NewGuid(); 
    } 

    #region IComparable<BaseEntity> Members 

    public int CompareTo(BaseEntity other) 
    { 
     if (ID == Guid.Empty || other.ID == Guid.Empty) 
      return _internalID.CompareTo(other._internalID); 

     return ID.CompareTo(other.ID); 
    } 

    #endregion 

    ... 

} 

Tenga en cuenta el uso de un campo ID interno. Esto es necesario para entidades nuevas (transitorias); de otro modo, no tendrán inicialmente una ID (mi modelo las ha suministrado cuando se guardaron).

+0

[Esta respuesta] (http://stackoverflow.com/questions/5266180/fighting-cartesian-product-x-join-when-using-nhibernate-3-0-0/5285739#5285739) me ayudó a ver cómo utilizar las consultas QueryOver y Future para buscar con entusiasmo a niños y nietos sin devolver duplicados. La técnica implica dividir la tarea en consultas SQL separadas que se ejecutan en una ida y vuelta a la base de datos. –

Respuesta

21

Obtiene 4XOrder y 4XLines porque la unión con líneas duplica los resultados.Puede configurar un transformador en el ICriteria como:

.SetResultTransformer(new DistinctRootEntityResultTransformer()) 
+3

Descubrí que esto realmente soluciona el problema, * si * las asignaciones se cambian de una bolsa a otra, e implemento el necesario IComparable en la clase base. –

+1

Interesante, he usado el DistinctRoot ... pero siempre con un Bag, y nunca implementé IComparable. Pero nunca fue para carga de 3 niveles :) – sirrocco

+0

Además, creo que sería mejor no Cargar al cliente y luego ir a Customer.Orders. En general, no le pide a un cliente que le diga sus pedidos. Por lo tanto, sería mejor tener un Repo y: GetOrdersForCustomerId (int id). – sirrocco

5

acabo de leer Ayende's Blogpost donde utilizó el siguiente ejemplo:

session.CreateCriteria(typeof(Post)) 
    .SetFetchMode("Comments", FetchMode.Eager) 
    .List(); 

en una consulta Criterios para evitar Lazy Loading en una determinada consulta

Tal vez eso puede ayudar.

+0

Usted acaba de ahorrarme un puñado de horas de prueba de regresión debido a este comentario. Muy agradecido. –

+1

Eso no funcionará "en varios niveles", como dice la pregunta. Usar FetchMode.Eager para múltiples niveles dará como resultado un producto cartesiano. Se genera el SQL correcto, pero NHibernate no lo resolverá por usted. –

0

@Tigraine: su consulta solo devuelve Publicación con comentarios. Esto trae todas las publicaciones con todos los comentarios (2 niveles). Lo que pide Ben es que el cliente ordene a LineItem (nivel 3). @Ben: que yo sepa, nHibernate no admite una carga ansiosa de hasta 3 niveles. Hibernate lo apoya tú.

+0

@Sheraz - Espero que estés equivocado :-) PERO, si tienes razón, ¿por qué generaría el SQL correcto? ¿Suerte? –

0

Estaba teniendo el mismo problema. Vea esto thread. No obtuve una solución, pero una pista de Fabio. Usa Set en lugar de bolsa. Y funcionó.

Así que mi sugerencia es intentar usar el conjunto. Usted no tiene que utilizar Iesi uso de la colección IDictonary y NH está feliz

public override IEnumerable<Baseline> GetAll() 
{ 
    var baselines = Session.CreateQuery(@" from Baseline b 
              left join fetch b.BaselineMilestones bm 
              left join fetch bm.BaselineMilestonePrevious ") 
              .SetResultTransformer(Transformers.DistinctRootEntity) 
              .List<Baseline>(); 
    return baselines; 
} 
+2

El cambio de a también me funcionó, pero un rastreo SQL muestra que un producto cartesiano es todavía producido en el servidor. Esto significa que NHibernate está clasificando y filtrando los resultados en su extremo, para poblar correctamente las colecciones de niños y nietos. Esto probablemente no sea ideal en muchas situaciones, ya que puede significar que se arrastran miles o millones de registros a través del cable, y luego un gran procesamiento para construir el gráfico de objeto correcto. –

1

Si usted necesita para mantener su uno-a-manys como bolsas, entonces se puede emitir 2 consultas, cada una con sólo 1 nivel de jerarquía . por ejemplo, algo como esto:

var temp = session.CreateCriteria(typeof(Order)) 
    .SetFetchMode("Lines", NHibernate.FetchMode.Eager) 
    .Add(Expression.Eq("Customer.ID", id)) 
    .List(); 

var customer = session.CreateCriteria(typeof(Customer)) 
    .SetFetchMode("Orders", NHibernate.FetchMode.Eager) 
    .Add(Expression.Eq("ID", id)) 
    .UniqueResult(); 

líneas se cargan en la memoria caché NH en la primera consulta, por lo que no necesitarán la carga diferida cuando se accede más tarde por ejemplo customer.Orders [0] .Lines [0].

Cuestiones relacionadas