Hoy Couchbase se complace en anunciar el lanzamiento GA del proveedor oficial LINQ para Couchbase Server y el lenguaje de consulta de moda para documentos JSON, ¡N1QL! El objetivo del proveedor es proporcionar un ORM/ODM simple y fácil de usar que se acerque más a Linq2SQL que EntityFramework o NHibernate, que son verbosos y pueden ser complejos.
Aunque la simplicidad es el objetivo, no subestimes el poder de Linq2Couchbase; ¡es una implementación de Linq completamente funcional con soporte extendido para todas las increíbles características de N1QL!
En este post repasaremos lo básico para empezar con Linq2Couchbase, los principales actores de la API, y la integración con ASP.NET y Owin/Katana. ¡En posts posteriores entraremos en más detalle sobre los pormenores y detalles de Linq2Couchbase!
Arquitectura
El proveedor es en realidad una capa más sobre el SDK; el proveedor se encarga del análisis sintáctico y la generación de consultas y el SDK se encarga de la solicitud y la asignación de los resultados. El proveedor utiliza Re-linq internamente para crear un árbol de sintaxis abstracta (AST) a partir de la consulta Linq, que luego se utiliza para emitir la sentencia N1QL. Ten en cuenta que Re-linq es utilizado tanto por NHibernate como por EntityFramework, ¡así que estás en buenas manos!
Primeros pasos
La fuente está disponible en Github y el paquete está disponible en NuGetSi utilizas el gestor de paquetes NuGet para instalar Linq2Couchbase, todas las dependencias, incluido el SDK de Couchbase.NET, serán gestionadas por ti.
Para instalar Linq2Couchbase utilizando el gestor de paquetes NuGet (suponiendo que ya has creado un proyecto de Visual Studio) abre el gestor de paquetes haciendo clic con el botón derecho del ratón en "Manage Nuget Packages" y buscando Linq2Couchbase o utilizando la línea de comandos del gestor de paquetes:
1 2 |
PM> Instale-Paquete Linq2Base |
Una vez hecho esto el proyecto tendrá todas las dependencias necesarias. A continuación tendrás que instalar Couchbase Server, ya sea localmente o a través de máquinas virtuales. El enlace de descarga de Couchbase Server es aquí. Para las máquinas virtuales, utilice vagabundos que utiliza Puppet y Vagrant para instalar un cluster de servidores Couchbase. ¡Asegúrate de instalar Couchbase 4.0! Si estás usando Vagrants, entonces aprovisiona el cluster:
1 2 3 |
pío@ELJEFE-PC ~/repos/vagabundos/4.0.0/debian7 (maestro) $ vagabundo arriba |
Una vez que tenga un Servidor o cluster Couchbase, configure el Servidor o Cluster y asegúrese de que al menos un nodo es un nodo Índice y un nodo es un nodo Consulta. Esto lo hará en el primer paso de la "Configuración del Servidor" o cuando añada un servidor adicional a su cluster. Además, añada el conjunto de datos "muestra-cerveza" al clúster durante la configuración o desde la pestaña Configuración>Muestras después de haber configurado el clúster o la instancia.
Ahora que tu instancia o cluster de Couchbase está configurado, necesitarás crear un índice primario en el bucket "beer-sample". Para ello, navega a C:Archivos de programaCouchbaseServerbin o si utiliza vagrants
(o linux) /opts/couchbase/bin
utilizando un símbolo del sistema. A continuación, escriba cbq o ./cbq (en linux) para iniciar la consulta CIL y luego:
1 2 |
CREAR PRIMARIO ÍNDICE EN `cerveza-muestra` USO DE GSI; |
Esto creará un índice primario en el cubo beer-sample. Fíjese en las comillas "`", son necesarias para escapar el "-" de beer-sample. Ahora está listo para escribir algo de código.
Creación del BucketContext
El objeto principal para trabajar con el bucket es el BucketContext. El BucketContext es análogo al DataContext en Linq2Sql y al DbContext en EntityFramework. Su propósito principal es proporcionar una interfaz para construir consultas y enviarlas al servidor Couchbase.
Puede utilizar BucketContext como un objeto independiente o puede derivar de él y crear un objeto fuertemente tipado que asigne propiedades a los tipos de su modelo de dominio y cubo. En este ejemplo haré esto último:
1 2 3 4 |
/// /// Un DbContext concreto para el cubo de ejemplo muestra-cerveza. /// |
public class CervezaMuestra : BucketContext { public CervezaMuestra() : this(ClusterHelper.GetBucket("cerveza-muestra")) { } public CervezaMuestra(IBucket bucket) : base(bucket) { DocumentFilterManager.SetFilter(new BreweryFilter()); } public IQueryable Cervezas { get { return Query(); } } public IQueryable Cervecerías { get { return Consulta(); } } }
El bucket de muestras de cerveza (un bucket es similar a una base de datos en un sistema RDBMS) contiene documentos que están "tipados" como "cervecería" y "cerveza", este sistema de tipado informal permitirá consultar el bucket y devolver documentos de cervecería o documentos de cerveza mediante un predicado(WHERE type="beer" por ejemplo). En el código anterior hemos definido propiedades explícitas que devuelven IQueryable
Ejemplo de consulta
Verás que utilizar Linq2Couchbase es prácticamente idéntico a Linq2SQL o al EF:
1 2 3 4 5 6 7 8 9 10 11 12 |
var db = nuevo CervezaMuestra(); var consulta = de cerveza en db.Cervezas únase a cervecería en db.Cervecerías en cerveza.BreweryId es igual a N1QlFunciones.Clave(cervecería) seleccione nuevo { cerveza.Nombre, cerveza.Abv, BreweryName = cervecería.Nombre }; foreach (var cerveza en consulta) { Consola.WriteLine(cerveza.Nombre); } |
Una vez que tengas una referencia BucketContext todo lo que tienes que hacer es consultarlo como lo harías con cualquier otro proveedor Linq. Todas las palabras clave de Linq están soportadas, así como las construcciones de N1QL como ON KEYS, NEST y UNNEST. En un post posterior, veremos todo esto con mucho más detalle.
El modelo de documento
En el contexto BeerSample anterior, los objetos Beer y Brewery serán los objetivos de nuestras proyecciones Linq y corresponden o se asignan a documentos JSON equivalentes en nuestro bucket (beer-sample). He aquí una lista de cada uno (tenga en cuenta que se trata de una lista parcial, las clases en su totalidad se puede encontrar aquí):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[Filtros.Filtro de tipo de documento("cerveza")] público clase CervezaFiltrada { [JsonProperty("nombre")] público cadena Nombre { consiga; configure; } [JsonProperty("abv")] público decimal Abv { consiga; configure; } ... [JsonProperty("categoría")] público cadena Categoría { consiga; configure; } [JsonProperty("actualizado")] público FechaHora Actualizado { consiga; configure; } } |
Por supuesto, esto corresponde a los documentos "cerveza"; observe el atributo DocumentTypeFilter. Esto añadirá "auto-mágicamente" un predicado o cláusula WHERE que filtra por el tipo "cerveza" a cada consulta que tenga como objetivo ese documento. El atributo DocumentTypeFilter es una de las dos formas de aplicar un filtro, a menos que añada manualmente el predicado a cada consulta.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
público clase Cervecería { [JsonProperty("nombre")] público cadena Nombre { consiga; configure; } [JsonProperty("ciudad")] público cadena Ciudad { consiga; configure; } ... [JsonProperty("geo")] público Geo Geo { consiga; configure; } [JsonProperty("cervezas")] público Lista Cervezas { consiga; configure; } } |
Este es el objeto al que se asignarán los documentos "cervecería". Observe que no hay ningún atributo DocumentTypeFilter definido explícitamente; esto se debe a que el constructor para el contexto BeerSample añadirá el filtro al DocumentFilterManager. Esto es puramente una aproximación diferente al mismo problema; añadir un predicado a una consulta para filtrar por tipo.
Integración con ASP.NET u Owin/Katana
Existe un patrón muy definido para usar el SDK de Couchbase .NET en proyectos ASP.NET o Katana/OWin. Dado que BucketContext utiliza el SDK de Couchbase .NET, tendrás que seguir este patrón para poder aprovechar la caché de objetos dentro del SDK y las conexiones TCP compartidas. Afortunadamente, es un patrón muy simple:
Uso de Global.asax en ASP.NET
En una aplicación ASP.NET que utilice Global.asax, aprovechará los manejadores de eventos Application_Start y Application_End para crear y destruir los objetos Cluster y Bucket de los que depende BucketContext.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
público clase MvcApplication : Sistema.Web.HttpApplication { protegido async void Inicio_de_la_aplicación() { ÁreaRegistro.RegistrarTodasLasÁreas(); FilterConfig.RegisterGlobalFilters(Filtros globales.Filtros); RouteConfig.RegisterRoutes(Tabla de rutas.Rutas); BundleConfig.RegisterBundles(BundleTable.Paquetes); var config = nuevo ClientConfiguration { Servidores = nuevo Lista { nuevo Uri("http://192.168.77.101:8091/") } }; ClusterHelper.Inicializar(config); var cubo = ClusterHelper.GetBucket("por defecto"); } protegido void Fin_aplicación() { ClusterHelper.Cerrar(); } } |
Aquí estamos creando la configuración (aunque también puede venir de App.Config) y luego inicializando el objeto ClusterHelper. Finalmente, cuando la aplicación se cierra, destruimos los objetos Cluster y Bucket en el manejador Application_End. Este será un cierre elegante y las construcciones a nivel de SO serán devueltas al SO de manera oportuna.
Uso de Setup.cs en Owin/Katana
En las aplicaciones alojadas en Owin/Katana, seguimos un patrón similar, sólo que utilizamos un método diferente, la clase Setup.cs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
público parcial clase Puesta en marcha { público void Configuración(IAppBuilder aplicación) { ConfigureAuth(aplicación); //inicializar el ClusterHelper ClusterHelper.Inicializar(nuevo ClientConfiguration { Servidores = nuevo Lista { nuevo Uri("http://localhost:8091/") } }); //Registra un callback que se deshaga del ClusterHelper al cerrar la aplicación var propiedades = nuevo AppProperties(aplicación.Propiedades); var ficha = propiedades.OnAppDisposing; si (ficha != CancellationToken.Ninguno) { ficha.Regístrese en(() => { ClusterHelper.Cerrar(); }); } } } |
Aquí creamos e inicializamos el ClusterHelper cuando el método Configuration se ejecuta al inicio y luego registramos un delegado que se disparará cuando la aplicación se cierre, cerrando nuestro ClusterHelper y liberando recursos.
Inyección en los controladores
El propio BucketContext adopta las características del patrón Unit of Work; puedes crear uno para cada petición y como el ClusterHelper gestiona las referencias (asumiendo que sigues los consejos anteriores) la instancia simplemente será GC'd al final de la petición.
La forma más sencilla de hacerlo es simplemente utilizando Inyección de dependencia (el patrón) dentro de sus controladores para crear la instancia cuando se crea el controlador, por ejemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
público clase InicioControlador : Controlador { privado CervezaMuestra _contexto; público InicioControlador() : este(nuevo CervezaMuestra(ClusterHelper.GetBucket("muestra de cerveza"))) { } público InicioControlador(CervezaMuestra contexto) { _contexto = contexto; } ... } |
Ahora, sólo tiene que utilizar el BucketContext dentro de sus métodos de acción:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
público AcciónResultado Índice() { var consulta = (de cerveza en db.Cervezas únase a cervecería en db.Cervecerías en cerveza.BreweryId es igual a N1QlFunciones.Clave(cervecería) seleccione nuevo { cerveza.Nombre, cerveza.Abv, BreweryName = cervecería.Nombre }).Toma(10); devolver Ver(consulta.ToList()); } |
Una vez más, dado que el contexto es un objeto ligero de corta duración, puedes extenderlo a la solicitud e inyectarlo allí reutilizándolo para todos los controladores invocados dentro de esa solicitud.
¿Qué se avecina?
Muy pronto, la siguiente característica importante será el seguimiento de cambios mediante proxies. Además, ¡espera correcciones de errores, mejoras de rendimiento y otras características destinadas a hacer de Linq2Couchbase un ODM/ORM ligero y con todas las funciones!
Si hay alguna función que desee, una corrección de errores o tal vez quiera contribuir, agradecemos todo tipo de comentarios, tanto buenos como malos.
Linq2Couchbase es un proyecto de código abierto impulsado por la comunidad, así que échale un vistazo y, si quieres contribuir, ¡hazlo!
Agradecimientos especiales
Un agradecimiento especial a todos los que han contribuido al proyecto (¡al fin y al cabo es de código abierto!), especialmente a Brant Burnett de Software Centeredge que han contribuido significativamente al proyecto y a la documentación de NuGet.