Dezembro de 2017

Volume 32 - Número 12

Pontos de Dados - Criação de aplicativos UWP para armazenamento de dados local e na nuvem

De Julie Lerman

Julie LermanEsse é a primeira parte de uma série de várias partes que mostrará como armazenar dados em um aplicativo que executa um aplicativo da Plataforma Universal do Windows (UWP), e também como armazenar dados de aplicativo na nuvem. Este artigo será voltado para o armazenamento local usando o Entity Framework Core (EF Core) e um banco de dados SQLite. As partes seguintes incluirão recursos para armazenar e recuperar dados de um aplicativo UWP da nuvem usando o Azure Functions e o Azure Cosmos DB.

No início, quando o EF Core ainda era chamado de EF7, eu aproveitei o seu novo recurso para executar não apenas em uma instalação completa do .NET Framework, mas também em dispositivos. No meu primeiro curso da Pluralsight sobre o EF7 (que foi concebido como uma versão prévia dos recursos que estavam sendo criados), eu criei um jogo pequeno e bobo chamado Cookie Binge. Ele rodava no Windows Phone 8 e como um aplicativo da Windows Store para Windows 8, armazenando seus dados localmente usando EF7 e SQLite. O jogo era um spin-off de um aplicativo de demonstração criado pela equipe do EF que tinha como objetivo capturar unicórnios. O jogo Cookie Binge permite que você coma biscoitos (clicando sobre eles) e quando termina de comer tudo que tem na sua frente, você indica se esse ataque de gula valeu a pena ou se você se sente culpado por ter se empanturrado com tantos biscoitos. O número de biscoitos que você come torna-se a sua pontuação e a sua escolha de “valeu a pena” ou “culpado” é associada à sua pontuação no histórico do jogo. É um jogo bobo sem nenhum objetivo, apenas a minha forma de explorar como incorporar o EF7/Core em um aplicativo móvel.

Quando o EF Core foi lançado em 2016, eu expandi o jogo para ser executado como um aplicativo da Plataforma Universal do Windows (UWP), que podia ser jogado em qualquer dispositivo que executasse o Windows 10. O recentemente lançado EF Core 2.0 agora depende do .NET Standard 2.0. A versão mais recente da UWP, que é voltada para o recém-lançado Windows 10 Fall Creators Update, também depende do .NET Standard 2.0, o que permite usar o EF Core 2.0 nessa nova geração de aplicativos UWP.

Isso significa que é hora de eu atualizar o aplicativo CookieBinge novamente, dessa vez para o EF Core 2.0. A única forma de fazer isso é atualizar também o aplicativo UWP com a nova versão do Windows 10 em mente. Lembre-se que o meu foco permanecerá no acesso aos dados, não na criação da interface do usuário do aplicativo para beneficiar-se dos recursos do Windows 10. Além disso, eu não falarei sobre os novos recursos do EF Core 2.0 neste exemplo. Em vez disso, eu vou falar da capacidade de combinar essas duas tecnologias.

Eu vou criar o aplicativo do zero, em vez de atualizar o aplicativo existente, para que você possa acompanhar todo o processo. Após atualizar o jogo para o EF Core 2.0 eu adicionarei um novo recurso para permitir o compartilhamento de pontuações com outros jogadores. Para poder fazer isso, além de armazenar as pontuações localmente no dispositivo onde está o jogo, o aplicativo aproveitará Azure Functions e o Azure Cosmos DB para que os usuários possam compartilhar as suas pontuações no mundo todo. Eu realizarei essas tarefas nas partes seguintes dessa série.

Seu ambiente de desenvolvimento

Apesar do EF Core pode ser criado e executado em ambientes de plataforma cruzada, a UWP é uma plataforma exclusiva do Windows, então você precisará fazer esse trabalho em um computador Windows. Para ser específico, você precisará do Windows 10 Fall Creators Update e do Visual Studio 2017 versão 15.4 (ou posterior) instalados. O Visual Studio não instala o SDK do .NET Standard 2.0, você tem que instalá-lo manualmente. Você encontrará os links para os downloads do .NET Standard em bit.ly/2mJArWx. Você verá que pode baixar o tempo de execução ou o SDK (que inclui o tempo de execução). Selecione os downloads do SDK adequados para o seu computador.

Certifique-se de instalar as ferramentas da UWP para Visual Studio, selecionando a carga de trabalho da UWP no instalador do Visual Studio. Se você não possui o build 16299 do SDK do Windows 10 habilitado na carga de trabalho, certifique-se de fazer isso.

Um aviso de um early adopter (adotante inicial)

Observe que no momento que escrevo este artigo, o EF Core 2.0 não está totalmente alinhado com a nova versão da UWP, apesar de algumas correções serão implementadas em breve com o patch 2.0.1. Por exemplo, existe atualmente um bug que ocorre durante a execução de consultas relacionais dentro de um projeto da UWP. O meu exemplo evita esse problema. Você pode acompanhar os problemas no repositório GitHub do EF Core GitHub em bit.ly/2xS35Mq.

Criando o novo projeto UWP

Após todas as ferramentas adequadas estarem instaladas, o Visual Studio exibirá um conjunto de modelos do projeto UWP em uma seção chamada Windows Universal. A partir desse conjunto de modelos eu criarei um novo aplicativo em branco (Universal Windows). O framework padrão é de 4.7.1 (apesar do .1 estar cortado na Figuta 1) e vou deixar o padrão como a versão base do .NET Framework para o meu aplicativo. Eu chamei o novo projeto de CookieBinge2.

Criando um projeto UWP a partir do modelo de aplicativo em branco
Criando um projeto UWP a partir do modelo de aplicativo em branco

Você será solicitado a selecionar a versão pretendida e uma versão mínima da UWP. Certifique-se de escolher Windows 10 Fall Creators Update para ambas. O número do build atual é 16299.

Após o projeto ser criado, você verá os arquivos e a pasta no Gerenciador de Soluções, conforme mostrado na Figura 2.

O projeto conforme inicialmente criado pelo modelo
Figura 2 O projeto conforme inicialmente criado pelo modelo

Como essa coluna é voltada para dados, não para a criação da interface do usuário, vou usar alguns atalhos para criar a dados e, em seguida, adicionarei as APIs e o código para o EF Core e suas atividades de persistência de dados.

Você encontrará o código necessário para copiar e colar no download associado a esta coluna. Para comeaçar, apague as imagens na nova pasta Ativos do projeto e, no seu lugar, copie as imagens da pasta Ativos (mostrada na Figura 3) fornecida no download.

As imagens que devem ser copiadas para a pasta Ativos
Figura 3 As imagens que devem ser copiadas para a pasta Ativos

Adicionando lógica de persistência com o EF Core 2.0

Você começará adicionando duas classes ao projeto, conforme a seguir.

A classe CookieBinge.cs não é muito mais do que uma DTO, contendo somente propriedades que precisam ser armazenadas. Não existe uma lógica real que precise ser realizada. Aqui está a classe:

public class CookieBinge {
  public Guid Id { get; set; }
  public DateTime TimeOccurred { get; set; }
  public int HowMany { get; set; }
  public bool WorthIt { get; set; }
}

A classe BingeService encapsula as tarefas que eu determinei que o jogo precisa realizar, para organizar as informações entre a interface do usuário e o armazenamento de dados. BingeService possui três métodos: RecordBinge (para persistir os resultados localmente), GetRecentBinges (para recuperar um subconjunto de pontuações do jogo) e ClearHistory (para limpar todos os resultados da pontuação local). Aqui está a classe antes da lógica desses métodos ser implementada:

public static class BingeService {
    public static void RecordBinge(
      int count, bool worthIt) { }
    public static IEnumerable<CookieBinge>    
      GetRecentBinges(int numberToRetrieve)
    public static void ClearHistory() {  }
  }

Para completar o serviço, eu preciso adicionar a funcionalidade de persistência. Aqui é onde entra o EF Core 2.0 para ler e armazenar os resultados do jogo no dispositivo. O EF Core possui um provedor para bancos de dados SQLite, que podem ser usados diretamente em vários dispositivos, então é o que eu usarei para essa demonstração.

Agora que escolhi o armazenamento de dados, posso adicionar o provedor do EF Core para SQLite no meu aplicativo e ele, por sua vez, pegará os pacotes do EF Core relacionados dos quais depende, incluindo o SQLite, se necessário. O EF Core não é mais um único grande pacote. Em vez disso, a lógico é dividida em várias assemblies, para que você possa minimizar o espaço ocupado no seu dispositivo (ou servidor ou qualquer outro lugar onde ele for implementado) apenas com o subconjunto de lógico necessário para o seu aplicativo.

No Visual Studio 2017, você usa o Gerenciador de Pacotes NuGet para adicionar pacotes NuGet nos seus projetos, visualmente ou através de comandos Powershell como install-package. Eu usarei os comandos PowerShell, o que significa abrir o Console do Gerenciador de Pacotes (Ferramentas | Gerenciar Pacotes NuGet | Console do Gerenciador de Pacotes). Como tenho somente um projeto na minha solução, o console deve indicar que CookieBinge2 é o projeto padrão, conforme mostrado na Figura 4. É nele que posso instalar o pacote do provedor SQLite.

Instalando o pacote SQLite na janela do Console do Gerenciador de Pacotes
Figura 4 Instalando o pacote SQLite na janela do Console do Gerenciador de Pacotes

Após a instalação ter sido concluída, você verá o pacote listado nas referências do projeto no Gerenciador de Soluções. Para os curiosos, se você forçar o Visual Studio a mostrar todos os arquivos, expanda a pasta obj e, em seguida, abra o arquivo project.assets.json, você será capaz de ver as dependências que foram extraídas pelo provedor SQLite, tais como Microsoft.EntityFrameworkCore, Microsoft.EntityFrameworkCore.Relational e outras. Com o EF Core e o provedor SQLite prontos, agora você pode criar o DbContext. Adicione um novo arquivo chamado BingeContest.cs e copie o código listado na Figura 5.

Figura 5 A classe DbContext para gerenciar persistência com o EF Core

using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Windows.Storage;
namespace CookieBinge2 {
  public class BingeContext:DbContext {
    public DbSet<CookieBinge> Binges { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder options){
      var dbPath = "CookieBinge.db";
      try {
        dbPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, dbPath);
      }
      catch (InvalidOperationException){
        #TODO handle this exception
      }
      options.UseSqlite($"Data source={dbPath}");    }
  }
}

Como este DbContext está no projeto UWP, estou aproveitando os recursos disponíveis tais como as APIs System.IO e Windows.Storage. O download para este artigo refletirá esse padrão. Por sua vez, uma outra versão do aplicativo na minha conta do GitHub (bit.ly/2liqFw5) possui a lógica de back-end e o EF Core em um projeto de biblioteca de classes do .NET Standard e usa a injeção de dependência para fornecer o caminho do arquivo para o contexto. Se você planeja usar as migrações do EF Core para evoluir o esquema de banco de dados e o modelo, você precisará usar essa arquitetura separada.

O contexto possui um único DbSet para interagir com os dados do CookieBinge. O código no método OnConfiguring permite que o EF Core saiba que ele usará o provedor SQLite e especifica um caminho de arquivo local para armazenar os dados no dispositivo onde o aplicativo está sendo executado.

Agora que o contexto existe, eu posso completar os métodos do BingeService. RecordBinge irá pegar os valores enviados pela interface do usuário, criar um objeto CookieBinge e passá-lo para o BingeContext para armazená-lo no banco de dados local:

public static void RecordBinge(int count, bool worthIt) {
  var binge = new CookieBinge {
                HowMany = count,
                WorthIt = worthIt,
                TimeOccurred = DateTime.Now};
  using (var context = new BingeContext()) {
    context.Binges.Add(binge);
    context.SaveChanges();
  }
}

O método GetRecentBinges irá recuperar os dados do banco de dados armazenado localmente usando o BingeContext e irá passá-lo de volta para a interface do usuário que fez a solicitação e possui um painel para exibir os dados:

public static IEnumerable<CookieBinge> GetRecentBinges(
      int numberToRetrieve) {
   using (var context = new BingeContext()) {
     return context.Binges
               .OrderByDescending(b => b.TimeOccurred)
               .Take(numberToRetrieve).ToList();
   }
}

O método final no BingeService, ClearHistory, esvazia totalmente o banco de dados local, no caso de você não querer ser atormentado pela sua comilança:

public static void ClearHistory() {
  using (var context = new BingeContext(){
    context.Database.EnsureDeleted();
    context.Database.EnsureCreated();
  }
}

Isso cuida da parte do back-end da lógica do jogo.

A interface do usuário e sua lógica

O front-end é formado pela interface do usuário (MainPage.xaml), lógica da interface do usuário (MainPage.xaml.cs) e pelo arquivo BingeViewModel.cs. Existem vários métodos nessas classes de interesse para a funcionalidade que eu criei no back-end. Em MainPage.xaml.cs, existem dois métodos que chamam a classe BingeService. O método ReloadHistory chama o método GetRecentBinges e vincula os resultados a uma lista na interface do usuário:

private void ReloadHistory() {
  BingeList.ItemsSource = BingeService.GetRecentBinges(5);
}

O método ClearHistory chama o método de serviço para limpar todos os dados do banco de dados local e, em seguida, força a interface do usuário a recarregar a exibição do histórico, que agora estará vazio:

private void ClearHistory_Click(object sender, RoutedEventArgs e) {
  BingeService.ClearHistory();
  ReloadHistory();
}

Também existem métodos na interface do usuário que respondem ao clique do usuário nos botões Vale a pena e Não vale a pena para armazenar os resultados, mas esses não chamam o serviço diretamente. Em vez disso, eles chamam o método BingeViewModel, StoreBinge, que, por sua vez, chama o método de serviço para armazenar os dados. O método StoreBinge chama primeiro o método de serviço, passando os dados a partir da interface do usuário:

BingeService.RecordBinge(_clickCount, worthIt);

O StoreBinge também faz outras mágicas na interface do usuário para limpar a comilança mais recente e preparar a interface do usuário para uma nova comilança. Você pode ver esse código adicional no download.

Essa separação de assuntos, que resulta em passar os dados de um arquivo para outro, permite que uma futura manutenção do aplicativo seja mais simples. Felizmente para mim, as alterções no código após fazer a transição do aplicativo do EF7 para EF Core 1 para EF Core 2 foram mínimas.

Há uma última tarefa a ser realizada, que é acionar o aplicativo para ter certeza de que o arquivo de banco de dados local existe. No arquivo App.xaml, adicione o código a seguir no método construtor depois de this.Suspending += OnSuspending:

using (var context = new BingeContext()) {
  context.Database.EnsureCreated();

Na arquitetura separada no GitHub onde você pode usar as migrações, você verá um comando Migrate após EnsureCreated para ter certeza de que todas as alterações do modelo foram aplicadas.

Executando o aplicativo

No Visual Studio, você escolhe a máquina local, ou o simulador, que abre uma janela separada que simula o seu computador local, para executar ou depurar o aplicativo. Existe também um emulador para HoloLens disponível em bit.ly/2p8yRMf. Eu vou executar o meu aplicativo somente em um computador local. A Figura 6 mostra como o jogo roda com uma dica da tela de fundo da área de trabalho em volta das bordas. Cada clique no biscoito exibie a palavra “Nom!” na seta do mouse e incrementa o contador de “Biscoitos comidos”. Quando estiver satisfeito, o usuário pode clicar no ícone azul de satisfeito ou no ícone de morto para terminar o jogo. A Figura 7 mostra a página de Pontuação, exibindo as cinco últimas pontuações junto com o botão Limpar histórico. Essa interace de usuário é apenas o que um nerd de dados conseguiu criar por conta própria. Para ter acesso a uma orientação adequada sobre como criar uma interface de usuário da UWP, você pode encontrar um ótimo lugar para começar em bit.ly/2gMgE74.

Deliciando-se com os biscoitos; seis consumidos até agora
Figura 6 Deliciando-se com os biscoitos; seis consumidos até agora

O histórico armazenado no banco de dados local através do EF Core
Figura 7 O histórico armazenado no banco de dados local através do EF Core

Em breve: Compartilhando com outros jogadores através do Azure Functions e do Cosmos DB

Se você conseguir não se sentir incomodado com a simplicidade do meu design e do meu joguinho, o foco dessa lição é que o EF Core agora pode trabalhar diretamente em um dispositivo, pois agora ele depende do :NET Standard, não do .NET Framework todo e a UWP é compatível com o .NET Standard. Isso significa que agora você pode usar o EF Core 2 nos aplicativos UWP em vários dispositivos Windows 10, tais como HoloLens, Xbox, Windows Mixed Reality e mais.

Eu usei o EF Core 2.0 para armazenar dados localmente no dispositivo onde o jogo CookieBinge está sendo jogado. Na minha próxima coluna eu me prepararei para um novo conjunto de funcionalidades para o jogo, conectando-o à Web. Usando o Azure Functions, o aplicativo será capaz de enviar pontuações para um banco de dados do Azure Cosmos DB, permitindo que os usuários comparem suas próprias pontuações entre os vários dispositivos UWP onde eles jogam, assim como comparar as suas comilanças de biscoitos com outros comilões do mundo todo. Eu posso me garantir no Azure Functions e no Azure Cosmos DB quando o meu jogo Cookie Binge dominar o mundo e precisar ser expandido com o clique de um botão.


Julie Lerman é Diretora Regional da Microsoft, MVP da Microsoft, mentora e consultora de equipes de software e reside nas colinas de Vermont. Você pode encontrá-la em apresentações sobre acesso de dados ou sobre outros tópicos em grupos de usuários e conferências em todo o mundo. Ela tem um blog em datafarm.com/blog e é autora de “Programming Entity Framework”, assim como de uma edição da DbContext e da Code First, todas da O’Reilly Media. Siga-a no Twitter em @julielerman e confira seus cursos da Pluralsight em juliel.me/PS-Videos.

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Clint Rutkas


Discuta esse artigo no fórum do MSDN Magazine