2011-02-10 20 views
18

Quiero hacer una búsqueda con NHibernate cuando estoy escribiendo una consulta Linq. Es fácil de hacer algo como esto:Contando con NHibernate + Linq + Future

return session.Query<Payment>() 
    .OrderByDescending(payment => payment.Created) 
    .Skip((page - 1)*pageSize) 
    .Take(pageSize) 
    .ToArray(); 

Pero con esto no consigue ninguna información sobre el número total de elementos. Y si solo hago un simple .Count(), eso generará una nueva llamada a la base de datos.

Encontré this answer que lo resolvió mediante el uso futuro. Pero usa Criteria. ¿Cómo puedo hacer esto con Linq?

+1

Uso Session.QueryOver lugar - se ahorra intelisense y 'compileability', y hay un método SelectCount. Si lo necesita, puedo proporcionar detalles del uso de él – Genius

+0

Sí, por favor, ¡QueryOver parece funcionar bien también! – Allrameest

+1

(detecté un error en mi solución y la eliminé para evitar confusiones. En breve publicaré la versión fija) –

Respuesta

30

La dificultad con el uso de futuros con LINQ es que las operaciones como conde ejecutan inmediatamente.

Como @vandalo descubrió, Count() después de ToFuture() realmente ejecuta el Conde en la memoria, lo cual es malo.

La única forma de obtener el conteo en una futura consulta LINQ es usar GroupBy en un campo invariable. Una buena opción sería algo que ya forma parte de los filtros (como una propiedad "IsActive")

He aquí un ejemplo suponiendo que tiene una propiedad tal en pago:

//Create base query. Filters should be specified here. 
var query = session.Query<Payment>().Where(x => x.IsActive == 1); 
//Create a sorted, paged, future query, 
//that will execute together with other statements 
var futureResults = query.OrderByDescending(payment => payment.Created) 
         .Skip((page - 1) * pageSize) 
         .Take(pageSize) 
         .ToFuture(); 
//Create a Count future query based on the original one. 
//The paged query will be sent to the server in the same roundtrip. 
var futureCount = query.GroupBy(x => x.IsActive) 
         .Select(x => x.Count()) 
         .ToFutureValue(); 
//Get the results. 
var results = futureResults.ToArray(); 
var count = futureCount.Value; 

Por supuesto, la alternativa es haciendo dos viajes de ida y vuelta, lo cual no es tan malo de todos modos. Todavía se puede volver a utilizar el IQueryable original, que es útil cuando se desea hacer la paginación en una capa de nivel superior:

//Create base query. Filters should be specified here. 
var query = session.Query<Payment>(); 
//Create a sorted, paged query, 
var pagedQuery = query.OrderByDescending(payment => payment.Created) 
         .Skip((page - 1) * pageSize) 
         .Take(pageSize); 
//Get the count from the original query 
var count = query.Count(); 
//Get the results. 
var results = pagedQuery.ToArray(); 

actualización (2011-02-22): Escribí un blog post sobre este tema y una solución mucho mejor.

+5

el método de extensión en su publicación de blog es una solución muy buena – Chad

-6

Ok, parece que debería estar trabajando en su caso, pero no he probado:

return session.QueryOver<Payment>() 
    .Skip((page - 1) * pageSize) 
    .Take(pageSize) 
    .SelectList(r => r.SelectCount(f => f.Id)) 
    .List<object[]>().First(); 

prueba primero antes de Upvoting;)

UPD: lo siento, como ahora te entiendo, necesita obtener el conteo de todos los artículos. Luego hay que ejecutar la consulta sin paginación:

return session.QueryOver<Payment>() 
    .SelectList(r => r.SelectCount(f => f.Id)) 
    .List<object[]>().First(); 
+0

Eso NO es lo que necesitaba. –

+1

@Diego Mijelshon, ¡guau! mi representante es 666 ahora, gracias! :) – Genius

+0

Ninguna de esas consultas hará lo que se requiere. Obtenga el conjunto de objetos paginado + obtenga un recuento de la cantidad total de objetos que cumplen los criterios. – Neal

1
var query = Session.QueryOver<Payment>() 
    .OrderByDescending(payment => payment.Created) 
    .Skip((page -1) * pageSize) 
    .Take(pageSize) 

Esto es algo que acabo de descubrir que el LINQ a NH maneja muy bien, la ToRowCountQuery elimina tomar/saltar de la consulta y hace un recuento de filas futuro.

var rowCount = query.ToRowCountQuery().FutureValue<int>(); 

var result = query.Future(); 

var asArray = result.ToArray(); 
var count = rowCount.Value(); 
+0

Funciona bien con SQL Server, pero no con SQL Server Compact Edition ... :( System.Data.SqlServerCe.SqlCeException: Hubo un error al analizar la consulta. [ Token line number = 2, Token line offset = 1, Token in error = SELECT] – Allrameest

+1

La pregunta específicamente solicitada para una solución linq, no QueryOver. –

+0

Extrañamente, ToRowCountQuery parece eliminar agrupaciones ... http://stackoverflow.com/ preguntas/8012966/torowcountquery-seem-to-ignore-groupings –

4

La siguiente publicación de blog tiene una implementación de ToFutureValue que funciona con LINQ.

http://sessionfactory.blogspot.com.br/2011/02/getting-row-count-with-future-linq.html

Tiene un pequeño error en la línea siguiente que debe ser cambiado a partir de esto.

var provider = (NhQueryProvider)source.Provider; 

A esto:

var provider = (INhQueryProvider)source.Provider; 

Después de aplicar el cambio puede utilizar Que consultas de esta manera:

var query = session.Query<Foo>(); 
var futureCount = query.ToFutureValue(x => x.Count()); 
var page = query.Skip(pageIndex * pageSize).Take(pageSize).ToFuture(); 
Cuestiones relacionadas