Map
En la anterior entrada hicimos la primera introducción de lo que considero uno de los puntos fuertes, a mayores del API cliente, de RavenDB y este no es otro que su sistema de consultas. En esta entrada, hicimos una introducción sobre los índices que utiliza por detras RavenDB y que, como ya sabemos, se basan en Lucene.NET. Pero, los índices que vimos, eran dinámicos o bien generados automáticamente por RavenDB, si detecta un numero alto de consultas sobre el temporal. Estos índinces dinámicos no suelen ser una buena solución porque pueden tener una alta latencia, se ejecutan al vuelo y por lo tanto pueden tener un tiempo de ejecución alto, y no son demasiado flexibles en cuanto a las necesidades que podemos tener, trabajo con datos espaciales, búsquedas de texto completo, proyecciones etc.
Un índice estático no es más que la definición de una estructura llamada Raven.Abstractions.Indexing.IndexDefinition cuyo cuerpo podemos ver a continuación:
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> IndexDefinition |
1 |
{ |
1 |
<span style="color: #0000ff">public</span> IndexDefinition(); |
1 |
|
1 |
<span style="color: #0000ff">public</span> IDictionary<<span style="color: #0000ff">string</span>, <span style="color: #0000ff">string</span>> Analyzers { get; set; } |
1 |
<span style="color: #0000ff">public</span> IList<<span style="color: #0000ff">string</span>> Fields { get; set; } |
1 |
<span style="color: #0000ff">public</span> IDictionary<<span style="color: #0000ff">string</span>, FieldIndexing> Indexes { get; set; } |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">bool</span> IsCompiled { get; set; } |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">bool</span> IsMapReduce { get; } |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">bool</span> IsTemp { get; } |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> Map { get; set; } |
1 |
<span style="color: #0000ff">public</span> HashSet<<span style="color: #0000ff">string</span>> Maps { get; set; } |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> Name { get; set; } |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> Reduce { get; set; } |
1 |
<span style="color: #0000ff">public</span> IDictionary<<span style="color: #0000ff">string</span>, SortOptions> SortOptions { get; set; } |
1 |
<span style="color: #0000ff">public</span> IDictionary<<span style="color: #0000ff">string</span>, FieldStorage> Stores { get; set; } |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> TransformResults { get; set; } |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> Type { get; } |
1 |
|
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">bool</span> Equals(IndexDefinition other); |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">bool</span> Equals(<span style="color: #0000ff">object</span> obj); |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">int</span> GetHashCode(); |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">byte</span>[] GetIndexHash(); |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> RemoveDefaultValues(); |
1 |
} |
Si observa la definición anterior, hay una serie de puntos muy interesantes, como son los miembros Map, Reduce, TransformResult y Analyzers. A lo largo de esta entrada, intentaremos, aunque sea de forma somera, ver todos y cada uno de ellos, seguramente juntándolos con algún otro elemento necesario. Aunque es posible crear directamente esta estructura, por lo general, la misma se suele crear a partir de una clase llamada IndexDefinitionBuilder, como podemos ver en el siguiente ejemplo de código.
1 |
<span style="color: #0000ff">using</span>(IDocumentStore store = <span style="color: #0000ff">new</span> DocumentStore(){ConnectionStringName=<span style="color: #006080">"Raven"</span>}.Initialize()) |
1 |
<span style="color: #0000ff">using</span> (IDocumentSession session = store.OpenSession()) |
1 |
{ |
1 |
store.DatabaseCommands.PutIndex(<span style="color: #006080">"PostByTitle"</span>, <span style="color: #0000ff">new</span> IndexDefinitionBuilder<Post>() |
1 |
{ |
1 |
Map = posts => from p <span style="color: #0000ff">in</span> posts |
1 |
select <span style="color: #0000ff">new</span> { p.Title } |
1 |
}); |
1 |
|
1 |
} |
Con esto acabamos de ordenar al servidor de RavenDB crear un nuevo índice, llamado PostByTitle,sobre la colección de posts, utilizando para ellos los métodos de comandos de base de datos que nos ofrece nuestra interface IDocumentStore. Para el índice anterior podemos ver como hacer la búsqueda de los documentos que nos interesan y a mayores sobre que campos vamos a hacer la búsqueda de nuestros documentos, en este caso solamente la propiedad Title.
Ahora, si quisiéramos hacer una consulta sobre este índice podríamos hacer algo tal cual lo que vemos en el siguiente tip de código:
1 |
RavenQueryStatistics statistcs; |
1 |
var result = session.Query<Post>(<span style="color: #006080">"PostByTitle"</span>) |
1 |
.Statistics(<span style="color: #0000ff">out</span> statistcs) |
1 |
.Where(p => p.Title != <span style="color: #006080">"O bruxo mobile..."</span>) |
1 |
.Take(100) |
1 |
.ToList(); |
Analyzers
Bueno, parece que más o menos está claro, sin embargo, seguro que a muchos se os ocurren algunas preguntas. La primera que a mi me vino a la mano, es como es la creación de ese índice en el campo Title, es decir, la tokenización de los valores se puede hacer de muchas maneras, desechando terminos terminales, pudiendo poner sinónimos, etc…. Seguramanete, esta pregunta me la hice porque no conocía demasiado bien el concepto de Analizadores en Lucene.NET. Pues bien, una vez comprendidos los analizadores la siguiente pregunta es ver ¿como podemos decirle a los índices de RavenDB que ciertos campos estén marcados por un analizador? Pues si revisamos el codigo de creación de nuestro índice podemos ver lo sencillo que resulta esta tarea, como se puede ver a continuación.
1 |
store.DatabaseCommands.PutIndex(<span style="color: #006080">"PostByTitle"</span>, <span style="color: #0000ff">new</span> IndexDefinitionBuilder<Post>() |
1 |
{ |
1 |
Map = posts => from p <span style="color: #0000ff">in</span> posts |
1 |
select <span style="color: #0000ff">new</span> { p.Title }, |
1 |
Analyzers = |
1 |
{ |
1 |
{pp=>pp.Title,<span style="color: #0000ff">typeof</span>(StandardAnalyzer).FullName} |
1 |
} |
1 |
|
1 |
}); |
Observe como el diccionario Analyzers nos permite, de una forma bastante sencilla, incorporar una analizador específico a uno de los campos del Map. Puesto que nos pide el nombre de un tipo de analizador una forma sencilla de hacerlo sin tener que acordarse de todo el fullname es referenciar al tipo y extraer su FullName. Este tipo, está en la librería Lucene.NET que tendrá que referenciar puesto que no forma parte del API cliente de RavenDB.
Bien, puesto que seguramente, si ha seguido este post y ha modificado el índice se habrá dado cuenta de que una vez creado no podemos volver a ejecutar PutIndex, puesto que esto falla, al existir uno lógicamente. Para prevenir estos casos y hacer el bootstraping de los índices de una forma más sencilla, RavenDB nos ofrece un mecanismo alternativo que pasaremos a comentar a continuación. Con el fin de facilitar la definición y reutilización de indices RavenDB nos propone definir los mismos en una clase que extienda AbstractIndexCreationTask<TDocument> como por ejemplo podría ser la siguiente:
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> PostByTitleIndex |
1 |
:AbstractIndexCreationTask<Post> |
1 |
{ |
1 |
<span style="color: #0000ff">public</span> PostByTitleIndex() |
1 |
{ |
1 |
Map = posts => from p <span style="color: #0000ff">in</span> posts |
1 |
select <span style="color: #0000ff">new</span> { p.Title }; |
1 |
|
1 |
Analyzers.Add(pp => pp.Title, <span style="color: #0000ff">typeof</span>(StandardAnalyzer).FullName); |
1 |
} |
1 |
} |
Ahora, para hacer el botstrapper solamente tendríamos que ejecutar la siguiente instrucción, sin preocuparnos si los indices son nuevos, modificaciones de los existentes o ya existen con esa misma definición.
1 |
IndexCreation.CreateIndexes(<span style="color: #0000ff">typeof</span>(PostByTitleIndex).Assembly, store); |
Reduce
Con lo anterior, aunque no sea muy profundamente, ya hemos terminado la parte referida al Map, pero como ya seguramente sabrá, y hemos dicho, esta no es la única parte importante de un índice puesto que también podemos especificar una función de Reduce. En palabras parcas, un Reduce es una paso posterior a la selección o mapeo de documentos, sobre la podemos hacer opero tstaciones ( asociativas ) de agregación. A mayores, la idea es que estas operaciones se puedan hacer sobre subconjuntos de los seleccionados, con el fin de paralelizarlas, por eso la consideración de que sean asociativas las funciones de agregación. Hay una muy buena referencia visual acerca de cual es la teoría de Map/Reduce en este post que seguro le introducirá en este aspecto de buena manera.
Una vez que nos hemos preocupado por saber que es un Reduce, concepto que por supuesto tenemos en todas las bases de datos documentales como MongoDB, por ejemplo, pasaremos a ver algún ejemplo de esta característica. Supongamos por ejemplo que deseamos obtener la suma de los comentarios agrupadas por identificador de blog, puesto que la “cuenta” es una operación asociativa, podríamos crear un índice como sigue:
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> CountByBlogIdIndex |
1 |
: AbstractIndexCreationTask<Post, RavenStaticIndex.CountByBlogIdIndex.CountByBlogIdIndexReduceResult> |
1 |
{ |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> CountByBlogIdIndexReduceResult |
1 |
{ |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> BlogId { get; set; } |
1 |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span> Count { get; set; } |
1 |
} |
1 |
|
1 |
<span style="color: #0000ff">public</span> CountByBlogIdIndex() |
1 |
{ |
1 |
Map = posts => from p <span style="color: #0000ff">in</span> posts |
1 |
select <span style="color: #0000ff">new</span> { p.BlogId,p.Comments.Count }; |
1 |
|
1 |
Reduce = result => from r <span style="color: #0000ff">in</span> result |
1 |
group r by r.BlogId |
1 |
into temp |
1 |
select <span style="color: #0000ff">new</span> { BlogId = temp.Key, Count = temp.Sum(rr=>rr.Count) }; |
1 |
} |
1 |
} |
Y podríamos consultar esto de la siguiente forma en el API de RavenDB.
1 |
<span style="color: #0000ff">using</span>(IDocumentStore store = <span style="color: #0000ff">new</span> DocumentStore(){ConnectionStringName=<span style="color: #006080">"Raven"</span>}.Initialize()) |
1 |
<span style="color: #0000ff">using</span> (IDocumentSession session = store.OpenSession()) |
1 |
{ |
1 |
IndexCreation.CreateIndexes(<span style="color: #0000ff">typeof</span>(CountByBlogIdIndex).Assembly, store); |
1 |
|
1 |
RavenQueryStatistics statistcs; |
1 |
var result = session.Query<RavenStaticIndex.CountByBlogIdIndex.CountByBlogIdIndexReduceResult, CountByBlogIdIndex>() |
1 |
.Statistics(<span style="color: #0000ff">out</span> statistcs) |
1 |
.Take(100) |
1 |
.ToList(); |
1 |
|
1 |
} |
Bueno, creo que son ya bastantes cosas como para ponerse a jugar sin esperar más no?? yo creo que si, o sea que dejaremos para otra entrada algunas cosillas que nos faltan como multi-maps, funciones de transformación etc. etc..
Saludos
Unai