Agregar una proyección a un criterio de NHibernate evita que realice la selección de entidad predeterminada

StackOverflow https://stackoverflow.com/questions/1644776

Pregunta

Estoy escribiendo un criterio de NHibernate que selecciona datos que admiten la paginación. Estoy usando la expresión COUNT (*) OVER () de SQL Server 2005 (+) para obtener el número total de filas disponibles, como sugerido por Ayende Rahien. Necesito ese número para poder calcular cuántas páginas hay en total. La belleza de esta solución es que no necesito ejecutar una segunda consulta para obtener el recuento de filas.

Sin embargo, parece que no puedo escribir un criterio de trabajo (Ayende solo proporciona una consulta HQL).

Aquí hay una consulta SQL que muestra lo que quiero y funciona bien. Tenga en cuenta que intencionalmente omití la lógica de paginación real para centrarme en el problema:

SELECT Items.*, COUNT(*) OVER() AS rowcount
FROM Items

Aquí está el HQL:

select
    item, rowcount()
from 
    Item item

Tenga en cuenta que la función rowcount () está registrada en un dialecto NHibernate personalizado y se resuelve en COUNT (*) OVER () en SQL.

Un requisito es que la consulta se exprese utilizando un criterio. Desafortunadamente, no sé cómo hacerlo bien:

var query = Session
    .CreateCriteria<Item>("item")
    .SetProjection(
       Projections.SqlFunction("rowcount", NHibernateUtil.Int32));

Cada vez que agrego una proyección, NHibernate no selecciona el elemento (como lo haría sin una proyección), solo el rowcount () mientras realmente necesito ambos. Además, parece que no puedo proyectar item como un todo, solo sus propiedades y realmente no quiero enumerarlas todas.

Espero que alguien tenga una solución para esto. Gracias de todos modos.

¿Fue útil?

Solución

Creo que no es posible en Criteria, tiene algunos límites.

Puede obtener la identificación y cargar elementos en una consulta posterior:

var query = Session
    .CreateCriteria<Item>("item")
    .SetProjection(Projections.ProjectionList()
       .Add(Projections.SqlFunction("rowcount", NHibernateUtil.Int32))
       .Add(Projections.Id()));

Si no le gusta, use HQL, también puede establecer el número máximo de resultados:

IList<Item> result = Session
    .CreateQuery("select item, rowcount() from item where ..." )
    .SetMaxResult(100)
    .List<Item>();

Otros consejos

Use CreateMultiCriteria.

Puede ejecutar 2 declaraciones simples con un solo golpe a la base de datos de esa manera.

Me pregunto por qué es obligatorio usar Criteria. ¿No puedes usar session.CreateSQLQuery? Si realmente debe hacerlo en una consulta, le habría sugerido que retirara los objetos Item y el recuento, como:

select {item.*}, count(*) over() 
from Item {item}

... de esta manera puede recuperar objetos Item de su consulta, junto con el conteo. Si experimenta un problema con el almacenamiento en caché de Hibernate, también puede configurar los espacios de consulta (cachés de entidad / tabla) asociados con una consulta nativa para que las entradas de caché de consultas obsoletas se borren automáticamente.

Si entiendo su pregunta correctamente, tengo una solución. Luché bastante con este mismo problema.

Permítanme describir rápidamente el problema que tuve, para asegurarme de que estamos en la misma página. Mi problema se redujo a la paginación. Quiero mostrar 10 registros en la interfaz de usuario, pero también quiero saber el total número de registros que coinciden con los criterios del filtro. Quería lograr esto usando la API de criterios NH, pero cuando agregué una proyección para el recuento de filas, mi consulta ya no funcionó y no obtuve ningún resultado (no recuerdo el error específico, pero suena como lo que tú estás recibiendo).

Aquí está mi solución (copiar y pegar desde mi código de producción actual). Tenga en cuenta que " SessionError " es el nombre de la entidad comercial para la que estoy recuperando datos paginados, de acuerdo con 3 criterios de filtro: IsDev, IsRead e IsResolved.

ICriteria crit = CurrentSession.CreateCriteria(typeof (SessionError))
    .Add(Restrictions.Eq("WebApp", this));

if (isDev.HasValue)
    crit.Add(Restrictions.Eq("IsDev", isDev.Value));

if (isRead.HasValue)
    crit.Add(Restrictions.Eq("IsRead", isRead.Value));

if (isResolved.HasValue)
    crit.Add(Restrictions.Eq("IsResolved", isResolved.Value));

// Order by most recent
crit.AddOrder(Order.Desc("DateCreated"));

// Copy the ICriteria query to get a row count as well
ICriteria critCount = CriteriaTransformer.Clone(crit)
    .SetProjection(Projections.RowCountInt64());
critCount.Orders.Clear();

// NOW add the paging vars to the original query
crit = crit
    .SetMaxResults(pageSize)
    .SetFirstResult(pageNum_oneBased * pageSize);

// Set up a multi criteria to get your data in a single trip to the database
IMultiCriteria multCrit = CurrentSession.CreateMultiCriteria()
    .Add(crit)
    .Add(critCount);

// Get the results
IList results = multCrit.List();

List<SessionError> sessionErrors = new List<SessionError>();
foreach (SessionError sessErr in ((IList)results[0]))
    sessionErrors.Add(sessErr);

numResults = (long)((IList)results[1])[0];

Entonces creo mi criterio base, con restricciones opcionales. Luego lo CLONO y agrego una proyección de recuento de filas a los criterios CLONADOS. Tenga en cuenta que lo clono antes agrego las restricciones de paginación. Luego configuré un IMultiCriteria para contener los objetos ICriteria originales y clonados, y utilizo el IMultiCriteria para ejecutar ambos. Ahora tengo mis datos de paginación del ICriteria original (y solo arrastré los datos que necesito a través del cable), y también un recuento bruto de cuántos registros reales coinciden con mis criterios (útil para mostrar o crear enlaces de paginación, o lo que sea). Esta estrategia me ha funcionado bien. Espero que esto sea útil.

Sugeriría investigar un transformador de resultados personalizado llamando a SetResultTransformer () en su sesión.

Cree una propiedad de fórmula en la asignación de clase:

<property name="TotalRecords" formula="count(*) over()" type="Int32" not-null="true"/>;

IList<...> result = criteria.SetFirstResult(skip).SetMaxResults(take).List<...>();
totalRecords = (result != null && result.Count > 0) ? result[0].TotalRecords : 0;
return result;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top