Hoje, a Couchbase tem o prazer de anunciar o lançamento da versão GA do provedor oficial de LINQ para o Couchbase Server e a linguagem de consulta quente para documentos JSON, N1QL! O objetivo do provedor é fornecer um ORM/ODM simples e fácil de usar que seja mais próximo do Linq2SQL do que o EntityFramework ou o NHibernate, que são detalhados e podem ser complexos.
Embora o objetivo seja a simplicidade, não subestime o poder do Linq2Couchbase; ele é uma implementação totalmente funcional do Linq com suporte estendido para todos os recursos incríveis do N1QL!
Nesta postagem, abordaremos os conceitos básicos para começar a usar o Linq2Couchbase, os principais atores da API e a integração com o ASP.NET e o Owin/Katana. Em postagens posteriores, entraremos em mais detalhes sobre as especificidades e os detalhes do Linq2Couchbase!
A arquitetura
O provedor é, na verdade, apenas outra camada sobre o SDK; o provedor lida com a análise e a geração de consultas e o SDK lida com a solicitação e o mapeamento dos resultados. O provedor usa Re-linq internamente para criar uma árvore de sintaxe abstrata (AST) a partir da consulta Linq, que é então usada para emitir a instrução N1QL. Observe que o Re-linq é usado tanto pelo NHibernate quanto pelo EntityFramework, portanto, você está em boas mãos!
Primeiros passos
A fonte está disponível em Github e o pacote está disponível em NuGetSe você usar o gerenciador de pacotes NuGet para instalar o Linq2Couchbase, todas as dependências, inclusive o Couchbase.NET SDK, serão tratadas para você.
Para instalar o Linq2Couchbase usando o gerenciador de pacotes NuGet (supondo que você já tenha criado um projeto do Visual Studio), abra o gerenciador de pacotes clicando com o botão direito do mouse em "Manage Nuget Packages" e procurando por Linq2Couchbase ou usando a linha de comando do gerenciador de pacotes:
1 2 |
PM> Instalar-Pacote Linq2Couchbase |
Depois de fazer isso, o projeto terá todas as dependências necessárias. Em seguida, você precisará instalar o Couchbase Server localmente ou por meio de uma VM. O link para download do Couchbase Server é aqui. Para as VMs, use vagabundos que usa o Puppet e o Vagrant para instalar um cluster de servidores Couchbase. Certifique-se de instalar o Couchbase 4.0! Se estiver usando Vagrants, provisione o cluster:
1 2 3 |
espreitar@ELJEFE-PC ~/repositórios/vagabundos/4.0.0/debian7 (mestre) $ vagabundo para cima |
Quando você tiver um servidor ou cluster do Couchbase, configure o servidor ou cluster e certifique-se de que pelo menos um nó seja um nó de índice e um nó seja um nó de consulta. Isso será feito na primeira etapa da "Configuração do servidor" ou quando você adicionar um servidor adicional ao cluster. Além disso, adicione o conjunto de dados "beer-sample" ao cluster durante a configuração ou na guia Settings>Samples (Configurações>Amostras) depois de configurar o cluster ou a instância.
Agora que sua instância ou cluster do Couchbase está configurado, você precisará criar um índice primário no bucket "beer-sample". Para fazer isso, navegue até C:Arquivos de programasCouchbaseServerbin ou, se estiver usando vagrants
(ou linux) /opts/couchbase/bin
usando um prompt de comando. Em seguida, digite cbq ou ./cbq (no Linux) para iniciar a consulta CIL e depois:
1 2 |
CRIAR PRIMÁRIO ÍNDICE ON `cerveja-amostra` USO GSI; |
Isso criará um índice primário no bucket beer-sample. Observe os sinais de retrocesso "`", pois eles são necessários para escapar do "-" em beer-sample. Agora você está pronto para escrever algum código!
Criando o BucketContext
O principal objeto para trabalhar com o bucket é o BucketContext. O BucketContext é análogo ao DataContext no Linq2Sql e ao DbContext no EntityFramework. Sua finalidade principal é fornecer uma interface para criar consultas e enviá-las ao servidor Couchbase.
Você pode usar o BucketContext como um objeto autônomo ou pode derivar dele e criar um objeto de tipagem forte que mapeie as propriedades para os tipos em seu modelo de domínio e de balde. Neste exemplo, farei a segunda opção:
1 2 3 4 |
/// /// Um DbContext concreto para o bucket de exemplo de amostra de cerveja. /// |
public class BeerSample : BucketContext { public BeerSample() : this(ClusterHelper.GetBucket("beer-sample")) { } public BeerSample(IBucket bucket) : base(bucket) { DocumentFilterManager.SetFilter(new BreweryFilter()); } public IQueryable Beers { get { return Query(); } } public IQueryable Breweries { get { return Query(); } } }
O bucket de amostras de cerveja (um bucket é semelhante a um banco de dados em um sistema RDBMS) contém documentos que são "tipificados" como "cervejaria" e "cerveja". Esse sistema informal de tipos permitirá consultar o bucket e retornar documentos de cervejaria ou documentos de cerveja por meio de um predicado (WHERE type="beer", por exemplo). No código acima, definimos propriedades explícitas que retornam IQueryable
Um exemplo de consulta
Você verá que o uso do Linq2Couchbase é praticamente idêntico ao do Linq2SQL ou do EF:
1 2 3 4 5 6 7 8 9 10 11 12 |
var db = novo CervejaAmostra(); var consulta = de cerveja em db.Cervejas unir-se cervejaria em db.Cervejarias em cerveja.BreweryId iguais N1QlFunções.Chave(cervejaria) selecionar novo { cerveja.Nome, cerveja.Abv, BreweryName (nome da cervejaria) = cervejaria.Nome }; antes de (var cerveja em consulta) { Console.WriteLine(cerveja.Nome); } |
Uma vez que você tenha uma referência ao BucketContext, basta consultá-lo como faria com qualquer outro provedor Linq. Todas as palavras-chave Linq são suportadas, bem como construções N1QL como ON KEYS, NEST e UNNEST! Em uma postagem posterior, abordaremos tudo isso com muito mais detalhes!
O modelo de documento
No contexto BeerSample acima, os objetos Beer e Brewery serão os alvos de nossas projeções Linq e correspondem ou mapeiam documentos JSON equivalentes em nosso bucket (beer-sample). Aqui está uma listagem de cada um (observe que esta é uma listagem parcial, as classes em sua totalidade podem ser encontradas aqui):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[Filtros.Filtro DocumentType("cerveja")] público classe CervejaFiltrada { [JsonProperty("name" (nome))] público string Nome { obter; definir; } [JsonProperty("abv")] público decimal Abv { obter; definir; } ... [JsonProperty("category" (categoria))] público string Categoria { obter; definir; } [JsonProperty("atualizado")] público DateTime Atualizado { obter; definir; } } |
É claro que isso mapeia os documentos "cerveja"; observe o atributo DocumentTypeFilter. Isso adicionará "automaticamente" um predicado ou cláusula WHERE que filtra pelo tipo "cerveja" a todas as consultas que têm como alvo esse documento. O atributo DocumentTypeFilter é uma das duas maneiras de aplicar um filtro, a menos que você adicione manualmente o predicado a cada consulta.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
público classe Cervejaria { [JsonProperty("name" (nome))] público string Nome { obter; definir; } [JsonProperty("cidade")] público string Cidade { obter; definir; } ... [JsonProperty("geo")] público Geo Geo { obter; definir; } [JsonProperty("cervejas")] público Lista Cervejas { obter; definir; } } |
Esse é o objeto para o qual os documentos da "cervejaria" serão mapeados. Observe que não há nenhum atributo DocumentTypeFilter definido explicitamente; isso ocorre porque o construtor do contexto BeerSample adicionará o filtro ao DocumentFilterManager. Essa é apenas uma abordagem diferente para o mesmo problema: adicionar um predicado a uma consulta para filtrar por tipo.
Integração com ASP.NET ou Owin/Katana
Há um padrão muito distinto para usar o SDK do Couchbase .NET em projetos ASP.NET ou Katana/OWin. Como o BucketContext usa o SDK do Couchbase .NET, você precisará seguir esse padrão para aproveitar o cache de objetos no SDK e as conexões TCP compartilhadas. Felizmente, é um padrão muito simples:
Usando o Global.asax no ASP.NET
Em um aplicativo ASP.NET que usa o Global.asax, você aproveitará os manipuladores de eventos Application_Start e Application_End para criar e destruir os objetos Cluster e Bucket dos quais o BucketContext depende.
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 classe MvcApplication : Sistema.Web.HttpApplication { protegida assíncrono vazio Início_do_aplicativo() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filtros); RouteConfig.RegistrarRotas(Tabela de rotas.Rotas); BundleConfig.RegisterBundles(Tabela de pacotes.Pacotes); var configuração = novo Configuração de cliente { Servidores = novo Lista { novo Uri("http://192.168.77.101:8091/") } }; Ajudante de cluster.Inicializar(configuração); var balde = Ajudante de cluster.GetBucket("default"); } protegida vazio Application_End() { Ajudante de cluster.Fechar(); } } |
Aqui estamos criando a configuração (a propósito, isso também pode vir do App.Config) e, em seguida, inicializando o objeto ClusterHelper. Finalmente, quando o aplicativo é encerrado, estamos destruindo os objetos Cluster e Bucket de longa duração no manipulador Application_End. Esse será um desligamento gracioso e as construções no nível do sistema operacional serão devolvidas ao sistema operacional em tempo hábil.
Uso do Setup.cs no Owin/Katana
Nos aplicativos hospedados na Owin/Katana, seguimos um padrão semelhante, apenas usamos um método diferente, a classe 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 classe Inicialização { público vazio Configuração(IAppBuilder aplicativo) { ConfigureAuth(aplicativo); //inicializar o ClusterHelper Ajudante de cluster.Inicializar(novo Configuração de cliente { Servidores = novo Lista { novo Uri("http://localhost:8091/") } }); //Registre uma chamada de retorno que descartará o ClusterHelper no encerramento do aplicativo var propriedades = novo Propriedades do aplicativo(aplicativo.Propriedades); var token = propriedades.OnAppDisposing; se (token != CancellationToken.Nenhum) { token.Registro(() => { Ajudante de cluster.Fechar(); }); } } } |
Aqui, criamos e inicializamos o ClusterHelper quando o método Configuration é executado na inicialização e, em seguida, registramos um delegado que será acionado quando o aplicativo for encerrado, fechando nosso ClusterHelper e liberando recursos.
Injetando em seus controladores
O BucketContext em si assume as características do padrão Unit of Work; você pode criar um para cada solicitação e, como o ClusterHelper gerencia as referências (supondo que você esteja seguindo as orientações acima), a instância será simplesmente GC no final da solicitação.
A maneira mais simples de fazer isso é simplesmente usar Injeção de dependência (o padrão) em seus controladores para criar a instância quando o controlador for criado, por exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
público classe HomeController : Controlador { privado CervejaAmostra _contexto; público HomeController() : este(novo CervejaAmostra(Ajudante de cluster.GetBucket("amostra de cerveja"))) { } público HomeController(CervejaAmostra contexto) { _contexto = contexto; } ... } |
Agora, basta usar o BucketContext em seus métodos de ação:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
público Resultado da ação Índice() { var consulta = (de cerveja em db.Cervejas unir-se cervejaria em db.Cervejarias em cerveja.BreweryId iguais N1QlFunções.Chave(cervejaria) selecionar novo { cerveja.Nome, cerveja.Abv, BreweryName (nome da cervejaria) = cervejaria.Nome }).Pegue(10); retorno Ver(consulta.ToList()); } |
Mais uma vez, como o contexto é um objeto leve e de curta duração, você pode definir o escopo para a solicitação e injetá-lo lá, reutilizando-o para todos os controladores invocados nessa solicitação.
O que está por vir?
Muito em breve, o próximo grande recurso será o Change Tracking usando proxies. Além disso, espere por correções de bugs, aprimoramentos de desempenho e outros recursos destinados a tornar o Linq2Couchbase um ODM/ORM leve e com todos os recursos!
Se houver um recurso que você deseja, uma correção de bug ou se quiser contribuir, agradecemos todos os tipos de feedback, tanto os bons quanto os ruins.
O Linq2Couchbase é um projeto de código aberto voltado para a comunidade, portanto, dê uma olhada nele e, se quiser contribuir, faça-o!
Agradecimentos especiais
Um agradecimento especial a todos os que contribuíram com o projeto (afinal, trata-se de código aberto!), especialmente a Brant Burnett de Software Centeredge que contribuíram significativamente para o projeto e a documentação do NuGet!