Raven DB VI, La hora de las consultas…

Por fin,  seguro que ha sido la frase de muchos al oir que ya llegábamos a las consultas… Bien, pues si, con esta entrada inauguramos el trabajo con las consultas en RavenDB. Después ya de unos cuantos posts sobre el tema creo que ya es hora de tocar una de las cosas más importantes y que mejor hace RavenDB, que es las consultas.

Como siempre, ponemos un pequeño modelo de clase para realizar los distintos ejemplos. En esta occasion reutilizaremos las tipicas Blog, Post y Comment que ya hemos visto otras veces, y daremos por supuesto que tenemos ya una cantidad de documentos incluídos en RavenDB antes de procesar las consultas.

public class Blog
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string About { get; set; }
}
 
public class Post
{
    public string Id { get; set; }
    public string BlogId { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public List<Comment> Comments { get; set; }
}
 
public class Comment
{
    public string Author { get; set; }
    public string Text { get; set; }
}

* Como ya se ha comentado alguna vez, los modelos de las entradas tienen una finalidad lectiva y por lo tanto no se tiene porque ajustar a la realidad

 

Por defecto, y en la práctica, toda consulta que hacemos a RavenDB representa un índice sobre el almacen de RavenDB utilizando el proyecto Lucene .NET. Lucene, es un motor de búsqueda basado en .NET, en realidad es un port de java, basado en un índice invertido que funciona realmente rápido y dispone de muchas características importantes.  Por suerte, RavenDB no nos obliga a conocer la sintaxis de Lucene, aunque aprendiendo Lucene entenderíamos muchos de los comportamientos del motor de consultas, puesto que nos ofrece un completo soporte de Linq, pudiendo escribir todas nuestras consultas en Linq y dejando al API de cliente que se encargue de la traducción de las mismas a la sintaxis de Lucene.

Con el fin de mostrar el primer ejemplo, supongamos el siguiente código.

using(IDocumentStore store = new DocumentStore(){ConnectionStringName="Raven"}.Initialize())
using (IDocumentSession session = store.OpenSession())
{
    var post = (from c in session.Query<Post>()
                where c.Title != "RavenDB"
                select c).ToList();
}

 

En la consulta anterior podemos ver un ejemplo de consulta dinámica tal cual la podríamos hacer contra otro almacén como una colección de objetos o una tabla con EF. Lógicamente, esta consulta genera una petición http al servidor de RavenDB, en concreto, la siguiente petición:

 

GET http://ravenserver/indexes/dynamic/Posts?query=-Title%253ARavenDB%2520AND%2520Title%253A*&start=0&pageSize=128&aggregation=None HTTP/1.1
Accept-Encoding: deflate,gzip
Content-Type: application/json; charset=utf-8

Si examinamos la trama podemos observar los siguientes elementos:

 

  • Por defecto, las consultas se hacen sobre un indice, en este caso, como no hemos especificado cual se hace sobre un índice dinámico
  • Por defecto las consultas nos protegen contra resultados potencialmente grande, agregando por defecto un parámetro llamado pageSize , cuyo valor por defecto es 128.

Untitled

 

Bien, la primera condición era algo que ya sabíamos, es decir, si RavenDB está basado en Lucene tiene que existir algún tipo de índece. RavenDB, puede generar un índice dinámico para aquellas consultas que no se hacen sobre un índice estático. A mayores, si considera que se ha utilizado una consulta un numero alto de veces, podría materializar el indice dinámico en un índice estático. Para provocarlo, realize esa consulta un número de veces y diríjase a nuestra herramienta de administración de RavenDB, Raven Management Studio y podrá ver algo similar a lo mostrado en la imagen de la derecha. Por supuesto, más adelante veremos como crear los índices de forma estática.

 

El otro punto que hacíamos notar es como RavenDB por defecto nos protegía de las consultas potencialmente grandes restringiendo el número de resultados a 128, puesto que, por defecto el valor de pageSize es este. Si, nosotros queremos traer un conjunto mayor de resultados podríamos hacer uso de este operador en la consulta,  de forma similar a como vemos a continuación:

 

 

using(IDocumentStore store = new DocumentStore(){ConnectionStringName="Raven"}.Initialize())
using (IDocumentSession session = store.OpenSession())
{
    var post = session.Query<Post>()
                      .Where(c=>c.Title != "RavenDB")
                      .Take(200)
                      .ToList();
}

 

Otro nivel de protección que se incluye en RavenDB es que, por defecto, tampoco nos permite obtener más de 1000 resultados por consulta. En este caso, la protección no está en el cliente sino en el servidor y por lo tanto, modificar esto no es tan sencillo como acabamos de ver con el operador Take. Para hacerlo, tenemos que incluir en la configuración del servidor una nueva clave de configuración que se llama MaxPageSize.

 

<add key="Raven/MaxPageSize" value="2000"/>

A mayores, y como elemento de valor, RavenDB incluye algunos métodos de extensión a nuestras consultas que nos permitirán desde customizar las consultas hasta obtener información acerca de las mismas. El primero de los métodos que veremos es Statistics, gracias al cual podremos obtener información acerca del número total de documentos en la base de datos y otra información de valor.

 

using (IDocumentSession session = store.OpenSession())
{
    RavenQueryStatistics statistics;
    var post = session.Query<Post>()
                      .Statistics(out statistics)
                      .Where(c=>c.Title != "RavenDB")
                      .Take(200)
                      .ToList();
 
 
}

Untitled2

RavenQueryStatistics, nos ofrece, como puede observar, la siguiente información:

 

  • El nombre del índice que se ha utilizado en la consulta
  • Si los datos son state
  • El número total de documentos en la base de datos
  • El número de documentos omitidos ( hablaremos sobre esto)
  • La fecha de ejecución del índice
  • La fecha, a partir de la cual los datos son state

 

 

Un elemento importante de estas estadísticas es el de SkippedResults. Este valor nos ofrece la información de aquellos elementos que no se han tenido en cuenta para calcular el número total de resultados. Por ejemplo porque se ha aplicado una claúsula Distinct. Con esto, queremos decir que por ejemplo, si estamos paginando y hemos obtenido una primera pagina, incluyendo 5 en el valor de SkippedResults la siguiente página tendria que pedirse de la siguiente forma: pageCount + SkippedResults.

Otro de los métodos que podemos incluir en nuestras consultas es el método de Customize. En esta ocasión, este método no nos permite obtener más información sino que, nos permite customizar cierto grado de comportamiento de nuestras consultas. Imáginese por ejemplo que usted en una consulta dinámica no puede permitirse la consulta de datos state,  pues bien, con Customize podemos indicar esto de una forma sencilla.

 

using(IDocumentStore store = new DocumentStore(){ConnectionStringName="Raven"}.Initialize())
using (IDocumentSession session = store.OpenSession())
{
    RavenQueryStatistics statistics;
    var post = session.Query<Post>()
                      .Statistics(out statistics)
                      .Customize(dqc=>dqc.WaitForNonStaleResultsAsOfNow())
                      .Skip(0)
                      .Take(10)
                      .ToList();
}

 

 

Si observa durante un momento los métodos de los que disponemos en IDocumentQueryCustomization , el tipo del parámetro de entrada en la Action que establecemos en Customize, podemos ver que disponemos de diferentes sobrecargas y posibilidades para la espera de resultados no state.

 

  • WaitForNonStaleResultsAsOf: Nos permite establecer una fecha de corte, que lógicamente no tiene porque ser en este momento, para el cual los datos no puedan ser stale. Por supuesto, también nos permite indicar un tiempo máximo de espera.
  • WaitForNonStaleResultsAsOfNow: Nos permite especificar la fecha de corte como ahora.
  • WaitForNonStaleResultsAsOfLastWrite: Nos permite especificar la fecha de corte como la última en la que se ha escrito por parte de la aplicación.
  • WaitForNonStaleResults: Nos permite indicar que el procesado de la query debe de esperar por resultados no stale. Podemos indicar un tiempo máximo de espera si deseamos no estar bloqueando at infinitum. Solamente se suele utilizar dentro de tests unitarios, tiene demasiadas implicaciones como para que exista su uso fuera de este ámbito.

 

La propina

Bueno, la entrada ya se acerca a su final. En la misma, hemos visto los principios de las consultas en RavenDB y los índices dinámicos. Para la siguiente entrada, hablaremos acerca de los índices estáticos, funciones map-reduce y analizadores. No obstante me gustaría dejar una pequeña propina…. fíjese en el siguiente código.

 

 

using(IDocumentStore store = new DocumentStore(){ConnectionStringName="Raven"}.Initialize())
using (IDocumentSession session = store.OpenSession())
{
    var post = session.Query<Post>()
                      .Include(p => p.BlogId)
                      .ToList();
}

 

Al igual que el método Include en EF, este método nos permite en RavenDB indicarle que además de la carga de los documentos de la consulte carge adicionalmente la referencia indicada, en nuestro caso el blog asociado, de esta manera, tras el siguiente request:

 

GET http://ravenserver/indexes/dynamic/Posts?query=&start=0&pageSize=128&aggregation=None&include=BlogId HTTP/1.1
Accept-Encoding: deflate,gzip
Content-Type: application/json; charset=utf-8

Podemos ver los siguientes resultados,  dónde además de los post correspondientes se incluyen los Blogs referenciados.

 

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
ETag: c171861e-497b-1bc0-a8c1-78c1da9f6070
Server: Microsoft-IIS/7.5
Raven-Server-Build: 573
X-AspNet-Version: 4.0.30319
Persistent-Auth: true
X-Powered-By: ASP.NET
Date: Mon, 19 Dec 2011 18:08:59 GMT
Content-Length: 1589

{"Results":[{"BlogId":"blogs/1","Title":"The curegame, making off!","Body":"This is the first post about thecuregame..","Comments":[],"@metadata":{"Raven-Entity-Name":"Posts","Raven-Clr-Type":"RavenQueryI.Post, RavenQueryI","@id":"posts/1","Temp-Index-Score":1.0,"Last-Modified":"2011-12-19T17:06:05.6520000","@etag":"00000000-0000-0500-0000-000000000005","Non-Authoritive-Information":false}},{"BlogId":"blogs/2","Title":"The curegame, making off!","Body":"For me, this is the first entry about...","Comments":[],"@metadata":{"Raven-Entity-Name":"Posts","Raven-Clr-Type":"RavenQueryI.Post, RavenQueryI","@id":"posts/2","Temp-Index-Score":1.0,"Last-Modified":"2011-12-19T17:06:05.6520000","@etag":"00000000-0000-0500-0000-000000000006","Non-Authoritive-Information":false}}],"Includes":[{"Title":"O Bruxo mobile!","About":"About this blog post...","@metadata":{"Raven-Entity-Name":"Blogs","Raven-Clr-Type":"RavenQueryI.Blog, RavenQueryI","@id":"blogs/1","Last-Modified":"2011-12-19T17:03:56.0000000","@etag":"00000000-0000-0400-0000-000000000002","Non-Authoritive-Information":false}},{"Title":"La masa, el ladrillo, la bota y el bocadillo!","About":"About this blog post...","@metadata":{"Raven-Entity-Name":"Blogs","Raven-Clr-Type":"RavenQueryI.Blog, RavenQueryI","@id":"blogs/2","Last-Modified":"2011-12-19T17:03:56.0000000","@etag":"00000000-0000-0400-0000-000000000003","Non-Authoritive-Information":false}}],"IsStale":false,"IndexTimestamp":"2011-12-19T17:06:05.6520000","TotalResults":2,"SkippedResults":0,"IndexName":"Temp/Posts","IndexEtag":"00000000-0000-0500-0000-000000000006"}

 

Saludos y hasta la siguiente entrada,

 

Unai

Published 19/12/2011 19:12 por Unai
Comparte este post:
http://geeks.ms/blogs/unai/archive/2011/12/19/raven-db-vi-la-hora-de-las-consultas.aspx