Este artigo foi traduzido por máquina.

Banco de dados de documentos NoSQL

Inserindo o RavenDB em um aplicativo ASP.NET MVC 3

Justin Schwartzenberger

Baixar o código de exemplo

Atenção para a movimentação de NoSQL está crescendo dentro do Microsoft.NET da comunidade do Framework, à medida que continuamos a ouvir das empresas compartilhar suas experiências de implementação do mesmo em aplicativos que sabemos e usar. Com isso conscientizou vem a curiosidade para se aprofundar e identificar como um armazenamento de dados NoSQL poderia fornecer benefícios ou outras soluções possíveis para o software que os desenvolvedores estão desenvolvendo atualmente. Mas em que você iniciar e a curva de aprendizado é difícil? Talvez uma preocupação ainda mais relevante: quanto tempo e esforço são necessárias para acionar uma nova solução de armazenamento de dados e começar a escrever código em relação a ele? Afinal, você tem o processo de instalação do SQL Server para um novo aplicativo para uma ciência, certo?

O Word atingiu o.NET comunidade nas asas de um raven sobre uma nova opção para uma implementação de camada de dados do tipo NoSQL. RavenDB (ravendb.net) um banco de dados do documento destina-se a.Plataforma de NET/Windows, empacotada com tudo o que precisa para começar a trabalhar com um armazenamento de dados relacionais. RavenDB armazena documentos como sem esquema JSON. Uma API RESTful existe para a interação direta com o armazenamento de dados, mas a real vantagem reside dentro do.NET cliente API que vem incluído com a instalação. Ele implementa o padrão de unidade de trabalhar e aproveita a sintaxe do LINQ para trabalhar com documentos e consultas. Se você já trabalhou com um mapeador objeto relacional (ORM) — como o Entity Framework (FE) ou o NHibernate — ou consumido um serviço de dados do WCF, você vai sentir em casa com a arquitetura da API para trabalhar com documentos em RavenDB.

A curva de aprendizado para colocar em funcionamento com uma instância de RavenDB é curto e bonito. Na verdade, a peça que pode exigir mais planejamento é a estratégia de licenciamento (mas, mesmo que seja mínimo). RavenDB oferece uma licença de código-fonte aberto para projetos que são também abrir a fonte, mas é necessária para projetos comerciais de origem fechada uma licença comercial. Detalhes de licença e os preços podem ser encontrados em ravendb. NET/licenciamento. O site informa que a licença livre está disponível para empresas de inicialização ou aqueles que procuram usá-lo em um projeto de origem não-comercial, fechadas. De qualquer forma, vale a pena verificar rapidamente as opções para compreender a implementação de longo prazo possível antes de qualquer desenvolvimento de protótipos ou seguro.

RavenDB incorporado e MVC

RavenDB pode ser executado em três modos diferentes:

  1. Como um serviço do Windows
  2. Como um aplicativo IIS
  3. Incorporado em um.NET Visual Basic

Os dois primeiros têm um processo de instalação bastante simples, mas vêm com alguma sobrecarga de estratégia de implementação. A terceira opção, incorporada, é extremamente fácil entre em funcionamento. Na verdade, há um pacote de NuGet disponíveis para ele. Uma chamada para o seguinte comando no Console do Gerenciador de Pacotes em 2010 de Visual Studio (ou procure o termo "ravendb" na caixa de diálogo Gerenciar pacotes de NuGet) fornecerá todas as referências necessárias para começar a trabalhar com a versão incorporada do RavenDB:

Install-Package RavenDB-Embedded

Detalhes do pacote podem ser encontrados no site da Galeria de NuGet em bit.ly/ns64W1.

Adicionando a versão incorporada do RavenDB a um aplicativo ASP.NET MVC 3 aplicativo é tão simple quanto adicionar o pacote por meio de NuGet e um local de diretório dos arquivos dar o armazenamento de dados. Porque ASP.NET applications tem um diretório de dados conhecidos na estrutura chamada App_Data e a maioria das empresas de hospedagem fornecem acesso de leitura/gravação ao diretório com pouca ou nenhuma configuração necessária, é um bom lugar para armazenar os arquivos de dados. Quando o RavenDB cria seu armazenamento de arquivo, ele cria um punhado de diretórios e arquivos no caminho de diretório fornecido a ele. Ele não criará um diretório de nível superior para armazenar tudo. Sabendo que, vale a pena adicionar o ASP.NET a pasta chamada App_Data através do menu de contexto do projeto em 2010 de Visual Studio e, em seguida, crie um subdiretório no diretório App_Data para os dados de RavenDB (consulte a Figura 1).

App_Data Directory Structure
Figura 1 estrutura do diretório App_Data

Um armazenamento de dados do documento é sem esquema por natureza, portanto, não é necessário para criar uma instância de um banco de dados ou configurar todas as tabelas. Depois que a primeira chamada para inicializar o armazenamento de dados é feita no código, os arquivos necessários para manter o estado de dados serão criados.

Trabalhar com a API do cliente RavenDB a interface com o armazenamento de dados requer uma instância de um objeto que implementa a interface Raven.Client.IDocumentStore para ser criado e inicializado. A API tem duas classes, DocumentStore e EmbeddedDocumentStore, que implementam a interface e podem ser usados dependendo do modo em que está executando o RavenDB. Deve haver apenas uma instância por armazenamento de dados durante o ciclo de vida de um aplicativo. Posso criar uma classe para gerenciar uma única conexão com a minha área de armazenamento de documentos que permitem acessar a instância do objeto IDocumentStore por meio de uma propriedade estática e tem um método estático para inicializar a instância (consulte a Figura 2).

Figura 2 classe para DocumentStore

public class DataDocumentStore
{
  private static IDocumentStore instance;
 
  public static IDocumentStore Instance
  {
    get
    {
      if(instance == null)
        throw new InvalidOperationException(
          "IDocumentStore has not been initialized.");
      return instance;
    }
  }
 
  public static IDocumentStore Initialize()
  {
    instance = new EmbeddableDocumentStore { ConnectionStringName = "RavenDB" };
    instance.Conventions.IdentityPartsSeparator = "-";
    instance.Initialize();
    return instance;
  }
}

O getter de propriedade estática verifica um campo particular backup estático para um objeto nulo e, se for null, ele lança um InvalidOperationException. Eu gero uma exceção aqui, em vez de chamar o método Initialize, para manter o código thread-safe. Se a propriedade da instância foram autorizada a fazer a chamada e o aplicativo confiam ao fazer referência à propriedade para fazer a inicialização, deve haver uma chance de que mais de um usuário foi atingido o aplicativo ao mesmo tempo, resultando em chamadas simultâneas para o método Initialize. Dentro da lógica do método Initialize, crio uma nova instância de Raven.Client.Embedded.EmbeddableDocumentStore e defina a propriedade ConnectionStringName com o nome de seqüência de conexão que foi adicionado ao arquivo Web. config pela instalação do pacote NuGet de RavenDB. O Web. config, defino o valor da seqüência de conexão para uma sintaxe que entende de RavenDB para configurá-lo para usar a versão incorporada de local de armazenamento de dados. Eu também mapear o diretório do arquivo para o diretório de banco de dados criado no diretório App_Data do projeto MVC:

<connectionStrings>
  <add name="RavenDB " connectionString="DataDir = ~\App_Data\Database" />
</connectionStrings>

A interface de IDocumentStore contém todos os métodos para trabalhar com o armazenamento de dados. Posso retornar e armazenar o objeto EmbeddableDocumentStore como uma instância do tipo de interface IDocumentStore para que eu tenha a flexibilidade de alterar a instanciação do objeto EmbeddedDocumentStore para a versão do servidor (DocumentStore), se eu quiser abandonar a versão incorporada. Dessa forma, todo o meu código de lógica que manipulará meu gerenciamento de objeto de documento será dissociado do conhecimento do modo em que está executando o RavenDB.

RavenDB irá criar chaves de identificação do documento em um formato de resto por padrão. Um objeto de "Item" obteria uma chave no formato "itens/104". O nome do modelo de objeto é convertido em minúscula e é pluralized e um único número de identidade de rastreamento é acrescentado após uma barra com cada nova criação de documentos. Isso pode ser problemático em um aplicativo de MVC, como a barra causará um novo parâmetro de rota a ser analisado. A API do cliente RavenDB fornece uma maneira para alterar a barra, definindo o valor de IdentityPartsSeparator. No meu método de DataDocumentStore.Initialize, estou definindo o valor de IdentityPartsSeparator para um traço antes que eu chamo o método Initialize no objeto EmbeddableDocumentStore, para evitar o problema de roteamento.

A adição de uma chamada ao método estático DataDocumentStore.Initialize do método Application_Start no arquivo Global.asax.cs do meu aplicativo MVC estabelecerá a instância de IDocumentStore na primeira execução do aplicativo, que tem esta aparência:

protected void Application_Start()
{
  AreaRegistration.RegisterAllAreas();
  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);
 
  DataDocumentStore.Initialize();
}

Aqui eu pode fazer uso do IDocumentStore objeto com uma chamada estática para a propriedade DataDocumentStore.Instance para trabalhar em objetos de documento do meu armazenamento de dados incorporados no meu aplicativo MVC.

Objetos RavenDB

Para obter uma compreensão melhor dos RavenDB em ação, criarei um aplicativo de protótipo para armazenar e gerenciar indicadores. RavenDB foi projetado para trabalhar com Plain Old CLR Objects (POCOs), portanto, não é necessário adicionar os atributos de propriedade para orientar a serialização. Criar uma classe que representa um indicador é bastante direto. Figura 3 mostra a classe do indicador.

Figura 3 classe do indicador

public class Bookmark
{
  public string Id { get; set; }
  public string Title { get; set; }
  public string Url { get; set; }
  public string Description { get; set; }
  public List<string> Tags { get; set; }
  public DateTime DateCreated { get; set; }
 
  public Bookmark()
  {
    this.Tags = new List<string>();
  }
}

RavenDB serializa os dados do objeto em uma estrutura JSON, quando ele vai para armazenar um documento. Será usado o conhecido "Id" propriedade nomeado para lidar com a chave de identificação do documento. RavenDB criará esse valor — desde que a propriedade Id estiver vazia ou nula ao fazer a chamada para criar o novo documento — e armazená-lo em um elemento @ metadados do documento (que é usado para lidar com a chave do documento no nível do armazenamento de dados). Ao solicitar um documento, o código da API do cliente RavenDB definirá a chave de identificação do documento para a propriedade Id ao carregar o objeto document.

A serialização JSON, de um documento do indicador de amostra é representada na seguinte estrutura:

{
  "Title": "The RavenDB site",
  "Url": "http://www.ravendb.
net",
  "Description": "A test bookmark",
  "Tags": ["mvc","ravendb"],
  "DateCreated": "2011-08-04T00:50:40.3207693Z"
}

A classe de indicador é preparada para funcionar bem com o armazenamento de documentos, mas a propriedade de marcas é feito para representar um desafio na camada de interface do usuário. Eu gostaria de permitir que o usuário digite uma lista de marcas separados por vírgulas em um campo de entrada de caixa de texto único e faça com que o fichário modelo MVC mapear todos os campos de dados sem qualquer código de lógica seeping em Minhas Exibições ou ações do controlador. Eu possa lidar com por meio de um fichário de modelo personalizado para o mapeamento de um campo de formulário chamado "TagsAsString" para o campo Bookmark.Tags. Primeiro, crio a classe binder do modelo personalizado (consulte a Figura 4).

Figura 4 BookmarkModelBinder.cs

public class BookmarkModelBinder : DefaultModelBinder
{
  protected override void OnModelUpdated(ControllerContext controllerContext,
    ModelBindingContext bindingContext)
  {
    var form = controllerContext.HttpContext.Request.Form;
    var tagsAsString = form["TagsAsString"];
    var bookmark = bindingContext.Model as Bookmark;
    bookmark.Tags = string.IsNullOrEmpty(tagsAsString)
      ?
new List<string>()
      : tagsAsString.Split(',').Select(i => i.Trim()).ToList();
  }
}

Em seguida, atualizar o arquivo de Globals.asax.cs para adicionar o BookmarkModelBinder para os fichários do modelo na inicialização do aplicativo:

protected void Application_Start()
{
  AreaRegistration.RegisterAllAreas();
  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);
 
  ModelBinders.Binders.Add(typeof(Bookmark), new BookmarkModelBinder());
  DataDocumentStore.Initialize();
}

Para tratar-se de preencher uma caixa de texto HTML com as marcas atuais no modelo, adicionarei um método de extensão para converter uma lista de <string> objeto para uma seqüência de caracteres separados por ponto-e-vírgula:

public static string ToCommaSeparatedString(this List<string> list)
{
  return list == null ?
string.Empty : string.Join(", ", list);
}

Unidade de trabalho

A API do cliente RavenDB baseia-se no padrão de unidade de trabalho. Para trabalhar em documentos do armazenamento de documentos, uma nova sessão precisa ser aberto; trabalho precisa ser feito e salvo; e a sessão precisa ser fechado. A sessão trata o controle de alterações e opera de maneira semelhante a um contexto de dados em que o EF. Aqui está um exemplo de criação de um novo documento:

using (var session = documentStore.OpenSession())
{
  session.Store(bookmark);
  session.SaveChanges();
}

Ele é ideal para que a sessão ao vivo durante a solicitação HTTP para que ele pode rastrear alterações, use o primeiro nível de cache e assim por diante. Criarei um controlador de base que usará o DocumentDataStore.Instance para abrir uma nova sessão no executar a açãoe, na a ação executada irá salvar as alterações e, em seguida, dispose do objeto session (consulte a Figura 5). Isso me permite fazer todo o trabalho desejado durante a execução do meu código de ação com uma instância única sessão aberta.

Figura 5 BaseDocumentStoreController

public class BaseDocumentStoreController : Controller
{
  public IDocumentSession DocumentSession { get; set; }
 
  protected override void OnActionExecuting(ActionExecutingContext filterContext)
  {
    if (filterContext.IsChildAction)
      return;
    this.DocumentSession = DataDocumentStore.Instance.OpenSession();
    base.OnActionExecuting(filterContext);
  }
 
  protected override void OnActionExecuted(ActionExecutedContext filterContext)
  {
    if (filterContext.IsChildAction)
      return;
    if (this.DocumentSession != null && filterContext.Exception == null)
      this.DocumentSession.SaveChanges();
    this.DocumentSession.Dispose();
    base.OnActionExecuted(filterContext);
  }
}

Controller do MVC e implementação de modo de exibição

As ações de BookmarksController irá trabalhar diretamente com o objeto de IDocumentSession da classe base e gerenciar todas as operações de Create, Read, Update e Delete (CRUD) para os documentos. Figura 6 mostra o código para o controlador de indicadores.

Figura 6 classe de BookmarksController

public class BookmarksController : BaseDocumentStoreController
{
  public ViewResult Index()
  {
    var model = this.DocumentSession.Query<Bookmark>()
      .OrderByDescending(i => i.DateCreated)
      .ToList();
    return View(model);
  }
 
  public ViewResult Details(string id)
  {
    var model = this.DocumentSession.Load<Bookmark>(id);
    return View(model);
  }
 
  public ActionResult Create()
  {
    var model = new Bookmark();
    return View(model);
  }
 
  [HttpPost]
  public ActionResult Create(Bookmark bookmark)
  {
    bookmark.DateCreated = DateTime.UtcNow;
    this.DocumentSession.Store(bookmark);
    return RedirectToAction("Index");
  }
   
  public ActionResult Edit(string id)
  {
    var model = this.DocumentSession.Load<Bookmark>(id);
    return View(model);
  }
 
  [HttpPost]
  public ActionResult Edit(Bookmark bookmark)
  {
    this.DocumentSession.Store(bookmark);
    return RedirectToAction("Index");
  }
 
  public ActionResult Delete(string id)
  {
    var model = this.DocumentSession.Load<Bookmark>(id);
    return View(model);
  }
 
  [HttpPost, ActionName("Delete")]
  public ActionResult DeleteConfirmed(string id)
  {
    this.DocumentSession.Advanced.DatabaseCommands.Delete(id, null);
    return RedirectToAction("Index");
  }
}

O IDocumentSession.Query <T> método na ação índice retorna um objeto de resultado que implementa a interface IEnumerable, para poder usar a expressão do LINQ OrderByDescending para classificar os itens e chamar o método ToList para capturar os dados ao meu objeto de retorno. O método IDocumentSession.Load na ação detalhes leva em um valor de chave de identificação do documento e desfaz a serialização do documento correspondente a um objeto do tipo de indicador.

O método Create com o atributo de verbo HttpPost define a propriedade CreateDate no item indicador e chama o método IDocumentSession.Store do objeto de sessão para adicionar um novo registro de documento para o armazenamento de documentos. O método Update com o verbo HttpPost pode chamar o método de IDocumentSession.Store também, porque o objeto Bookmark terá o valor da Id já definida. RavenDB reconhecerá que Id e atualização existente documentam com a tecla correspondente em vez disso, a criação de um novo. A ação DeleteConfirmed chama um método de exclusão do objeto IDocumentSession.Advanced.DatabaseCommands, que fornece uma maneira para excluir um documento pela chave sem ter que carregar o objeto primeiro. Não preciso chamar o método IDocumentSession.SaveChanges de dentro de uma das seguintes ações, porque tenho um controlador de base, tornando essa chamada em a ação executada.

Todas as exibições são bem simples. Eles podem ser digitados fortemente para a classe de indicador em marcações de criar, editar e excluir e uma lista de marcadores na marcação do índice. Cada modo de exibição diretamente pode referenciar as propriedades do modelo para exibição e campos de entrada. O único local onde também precisará variam em referência de propriedades do objeto é com o campo de entrada para as marcas. Usarei o método de extensão ToCommaSeparatedString nas exibições de criar e editar com o seguinte código:

@Html.TextBox("TagsAsString", Model.Tags.ToCommaSeparatedString())

Isso permitirá que o usuário inserir e editar as marcas associadas ao indicador em um formato delimitado por vírgula dentro de uma caixa de texto único.

A pesquisa de objetos

Com todas as minhas operações CRUD no lugar, posso pode ativar minha atenção à última parte da funcionalidade de adição: a capacidade de filtrar a lista de indicadores por marcas. Para além de implementar a interface IEnumerable, o objeto de retorno do método IDocumentSession.Query também implementa o IOrderedQueryable e IQueryable interfaces da.NET Framework. Isso me permite usar o LINQ para filtrar e classificar minhas consultas. Por exemplo, eis uma consulta os indicadores criados nos últimos cinco dias:

var bookmarks = session.Query<Bookmark>()
  .Where( i=> i.DateCreated >= DateTime.UtcNow.AddDays(-5))
  .OrderByDescending(i => i.DateCreated)
  .ToList();

Aqui está um para percorrer a lista completa de indicadores:

var bookmarks = session.Query<Bookmark>()
  .OrderByDescending(i => i.DateCreated)
  .Skip(pageCount * (pageNumber – 1))
  .Take(pageCount)
  .ToList();

RavenDB criará os índices de dinâmicas com base na execução das consultas que serão mantidas por"algum tempo" antes de sendo descartados. Quando uma consulta semelhante é executada novamente com a mesma estrutura de parâmetro, será usado o índice dinâmico temporário. Se o índice é usado suficiente em um determinado período, o índice se tornarão permanente. Esses persistirá além do ciclo de vida do aplicativo.

Posso adicionar o seguinte método de ação para a minha classe BookmarksController para lidar com a obtenção de indicadores por marca:

public ViewResult Tag(string tag)
{
  var model = new BookmarksByTagViewModel { Tag = tag };
  model.Bookmarks = this.DocumentSession.Query<Bookmark>()
    .Where(i => i.Tags.Any(t => t == tag))
    .OrderByDescending(i => i.DateCreated)
    .ToList();
  return View(model);
}

Espero que essa ação seja atingido regularmente por usuários do meu aplicativo. Se o que é realmente verdade, essa consulta dinâmica será obter transformada em um índice permanente por RavenDB com nenhum trabalho adicional necessário da minha parte.

Um Raven é enviado para ser ativada nos

Com o surgimento de RavenDB, o.NET comunidade parece finalmente ter uma solução de tipo de repositório de documentos NoSQL catered em direção a ele, permitindo que as lojas centrados na Microsoft e desenvolvedores para planar através do mundo relacionais que tantas outras estruturas e idiomas têm sido navegar nos últimos anos. Nevermore deverá ouvimos os cries a falta de amor relacionais para a pilha de Microsoft. RavenDB está facilitando.NET possam iniciar a reprodução e desenvolvimento de protótipos com um armazenamento de dados relacionais, reunindo a instalação com o cliente API que imita a técnicas de gerenciamento de dados que os desenvolvedores já estão empregando limpo. Embora o argumento eterno entre relacionais e relacionais certamente não the out, a facilidade de experimentar algo "novo" deve ajudar a levar a uma melhor compreensão de como e onde uma solução relacionais cabe em uma arquitetura de aplicativo.

Justin Schwartzenberger Tem sido reconhecido CTO na DealerHosts, no desenvolvimento de aplicativos da Web para um certo tempo, atravessando os jungles sintáticas do PHP, VB. clássico de ASP, Visual Basic,NET e ASP.NET Web Forms. Como pioneira do ASP.NET MVC em 2007, ele decidiu Refatorar seu foco de pilha da Web para todas as coisas MVC. Ele contribui com artigos, palestras em grupos de usuários, mantém um blog em iwantmymvc.com e pode ser seguido no Twitter no twitter.com/schwarty.

Graças ao especialista técnico seguir pela revisão deste artigo: Ayende Rahien