Windows Phone 8.1 com Banco de Dados no SQL Server Compact 3.5/4

Renato Haddad

Dn690241.060DE5057573180CEC6D227C6D3E2207(pt-br,MSDN.10).png

Maio, 2014

Você já pensou em usar um banco de dados no Windows Phone 8.1? Por que algumas aplicações locais que não necessitam estar online o tempo todo fazem uso do banco local? Será que o processamento local, com informações armazenadas no dispositivo são mais rápidas que ler de um serviço na web? Algumas perguntas como estas tenho recebido e resolvi escrever um artigo para mostrar como usar o fantástico SQL Server Compact no Windows Phone 8 ou 8.1.

Os pré-requisitos para este artigo são conhecimento básicos de C#, LINQ e o Visual Studio .NET 2013 Update 2.

SQL Server Compact 3.5/4

O SQL Compact existe há anos, tem com base o SQL Server CE (Compact Edition) e atualmente na versão 4 está muito maduro, cheio de recursos, integrado com o LINQ (Language Integrated Query), gera todo tipo de Script, enfim, vale a pena consulta a documentação do produto.

E, como usarei o Visual Studio .NET 2013 Ultimate Update 2, é preciso instalar duas ferramentas fundamentais, pois sem elas não há progresso no exemplo:

  1. SQL Server Compact Toolbox – esta ferramenta tem suporte ao SQL Compact 3.5 e 4.0 e pode ser baixada em https://sqlcetoolbox.codeplex.com/ . Ela é integrada ao VS 2013 e nos dá todos os recursos que precisamos para fazer tudo com o banco de dados.
  2. SQL Server Compact 3.5 SP2 (Service Pack 2) – baixe em

http://www.microsoft.com/en-US/download/confirmation.aspx?id=5783 , descompacte numa pasta e instale o SSCERuntime_x86-ENU 32bits. Com isto, você terá acesso ao Compact 3.5 na Toolbox do SQL Server Compact. Mas por que instalar a versão 3.5 se já há a versão 4? É simples, a versão 4 ainda não está integrada a parte do Windows Phone para gerar os códigos em C# do contexto e das entidades que necessitamos. Por favor, observe que isto é neste momento em que estou escrevendo este artigo, pois se você um dia testar na versão 4 e estiver disponível a opção que verá logo a seguir, ótimo, é porque já está implementada.

Pergunta: Mas eu tenho o VS 2013 Update 2, projetos de Windows Phone 8/8.1 e o SQL Server Compact 4 instalados. Isto não é o suficiente para criar um banco de dados e os respectivos scripts? Não é o suficiente infelizmente, justamente porque nem tudo está habilitado.

Para uma explicação visual, abra o VS 2013, clique no menu Tools / SQL Compact Toolbox. Em alguns passos neste artigo, você verá passo a passo como criar um banco de dados, mas note na figura 1 que o banco já existe, e quando desejo gerar os código para o contexto (DataContext) no Windows Phone, a opção (Add Windows Phone DataContext to current Project) encontra-se desabilitada. Sendo assim, o correto é instalar o Service Pack 2 do SQL Server Compact 3.5, aí sim, a opção ficará habilitada.

Dn690241.C16C49D98344C9F36CEBDCCA7DF5C10E(pt-br,MSDN.10).png

Figura 1 – Opções desabilitadas do contexto

Uma vez instalados os pré-requisitos que citei, vamos ao passo a passo para criar o projeto. Crie um novo projeto em Visual C# / Store Apps / Windows Phone Apps com o template Databound App (Windows Phone Silverlight). O nome do projeto é DatabaseWP8 e você pode grava-lo em qualquer pasta que quiser, conforme a figura 2. A vantagem de usar este template é que ele já cria as páginas prontas, mas nada o impede de adicionar um novo item em um projeto atual ou ainda criar um projeto em branco e depois montar as páginas.

Dn690241.A210CA2780073B40B6B36561A8F85260(pt-br,MSDN.10).png

Figura 2 – Novo projeto de Windows Phone

Clique em OK e selecione o sistema operacional Windows Phone 8 ou 8.1, tanto faz, pois funciona com ambos. Com o projeto criado, vamos criar o banco de dados. Abra a SQL Server Compact Toolbox (menu Tools / SQL Server Compact Toolbox), clique com o botão direito em SQL Server Compact Data Connections e selecione Add SQL Server Compact 3.5 Connection, conforme a figura 3.

Pergunta: Posso selecionar a opção Add SQL Server Compact 4.0 Connection? Não, não pode porque o projeto é de Windows Phone e não há como gerar os scripts depois. Portanto, use o 3.5.

Dn690241.2F35B815B761DAC426FB483B22D92275(pt-br,MSDN.10).png

Figura 3 – Criar o banco de dados no SQL Compact 3.5

Na janela aberta, você tem duas opções: Browse para escolher um banco de dados existente; ou Create para criar um banco de dados. Como ainda não o temos, clique em Create, conforme a figura 4.

Dn690241.1B5057C6E7B0E83B05787D3BCF659206(pt-br,MSDN.10).png

Figura 4 – Criar ou selecionar um banco de dados

Como selecionamos Create, é aberta uma janela para você selecionar a pasta em que o banco será criado, neste caso, o diretório root do projeto. Em Nome, digite Banco, que será o nome do banco de dados, em Tipo deixe o SQL Server Compact Database (*.sdf) selecionado. Ao final, clique em Salvar, conforme a figura 5.

Dn690241.A5DA2EFC652AC6037A714C49F7506858(pt-br,MSDN.10).png

Figura 5 – Opções para criação do banco de dados

Note que automaticamente em Filename é mostrado o caminho completo de onde o banco será criado. Observe que a extensão do arquivo é SDF. Aqui você pode determinar o tamanho máximo do banco de dados em megabytes (Max database size in MB), que pode ser de 16 a 4091. Ou seja, o máximo é de aproximadamente 4 Gigabytes de informações, excelente não acha? Em Password você pode definir uma senha para a string de conexão, neste exemplo, deixe em branco, conforme a figura 6. Para casos necessários do uso da string de conexão, você já pode visualizar como que a mesma deve ser declarada. No entanto, isto depende do projeto e, no caso do Windows Phone 8/8.1 e Windows 8/8.1, é usado o recurso de Isolated Storage, ou seja, a declaração para se declarar no código C# é diferente. Mas, calma porque no momento certo do contexto eu explicarei isto. Clique em OK para criar o banco.

Dn690241.2F72F00F87E236F9526E9E8FCF1BB7BB(pt-br,MSDN.10).png

Figura 6 – Definições das propriedades do banco de dados

Uma vez criado o banco, o mesmo será exibido na lista de bancos no SQL Server Compact Toolbox. Neste caso é óbvio que ainda não contém nenhuma tabela. Então, dê um clique com o botão direito em Tables e selecione Build Table, conforme a figura 7.

Dn690241.1D689CCA378BA48EB73A636481F38518(pt-br,MSDN.10).png

Figura 7 – Criar tabela

Será aberta uma janela já conhecida por muitos, onde você deverá digitar o nome da tabela, neste caso Velejadores, e os demais campos com as propriedades, tipo de dados, tamanho, se permite ou não nulo, se é chave primária, qual o valor default, se é Identity, qual a precisão e a escala. Um dado interessante aqui é que o primeiro campo, o id, já é default. Caso queira alterar, fique à vontade. Peço que para este exemplo seja criada a tabela com a sua estrutura conforme a figura 8, contendo apenas 3 campos. Os botões Add e Delete são respectivamente para incluir ou excluir campos da entidade.

Dn690241.98BF9F6B1A8B0555C6B7541A55599642(pt-br,MSDN.10).png

Figura 8 – Estrutura da tabela

Como é um primeiro contato, prefiro criar entidades simples, sem relacionamentos e outros tipos de campos, afinal o foco do artigo é mostrar como criar um banco de dados e não as funcionalidades do banco, assim facilita o entendimento. O próximo passo é visualizar o script para gerar a entidade, então, clique no botão Script para gerar o T-SQL. Veja na figura 9 que fantástico, o VS já monta todo o T-SQL necessário para criar fisicamente a entidade. Nesta mesma tela de Script, há diversas opções no menu, então, clique no Execute para efetivar o Script no banco. Pronto, a entidade foi criada com sucesso, afinal o resultado está estampado logo abaixo nas mensagens: 0 rows affected.

Dn690241.3CE6EE181BA0A3AAEC7B734A4979E04B(pt-br,MSDN.10).png

Figura 9 – Criar o Script e a entidade fisicamente

Será que todas as colunas foram criadas? Com certeza sim, abra a entidade Velejadores e note toda a estrutura de acordo com os 3 campos declarados anteriormente. Que tal já inserirmos alguns dados na entidade? Ótima idéia, e para isto, clique com o botão direito em Velejadores e selecione Edit Top 200 rows. Adicione dados diretamente na entidade, conforme a figura 10. Claro que o Id você não deverá digitar por é automático.

Dn690241.3445744642BB8D958EA00F8629CE4497(pt-br,MSDN.10).png

Gerar o DataContext

O que é o DataContext? O DataContext é o contexto de um modelo de objeto relacional, onde podemos ter diversas entidades no mesmo contexto. Para você ter uma idéia, tenho projetos que usam cerca de 250 entidades no mesmo contexto no Entity Framework 6.1. Sendo assim, posso ter manipulações de dados das entidades na memória, todos no mesmo contexto, e quando peço para salvar o contexto, o .NET se encarrega de fazer os updates, deletes e inserts no banco de dados.

Costumo dizer que gerar o contexto é um dos passos mais importantes num projeto. Sendo assim, na SQL Server Compact Toolbox, clique com o botão direito no Banco.sdf, selecione Generate Code (EF/LINQ to SQL/SyncFX) / Add Windows Phone DataContext to current Project (needs 3.5), conforme a figura 11. Se esta opção não estiver disponível é porque você não instalou o Service Pack 2 do SQL Server Compact 3.5.

Dn690241.36C7E79EB46DE7A514E9F770F09E4DEF(pt-br,MSDN.10).png

Figura 11 – Geração do contexto

Será aberta uma janela conforme a figura 12 questionando o nome do contexto (BancoContext é a sugestão e deixe assim), o Namespace a ser usado (pode deixar este pois é o mesmo do projeto e isto facilita as referências), a linguagem será o C#, nem precisa alterar. A opção ”Pluralize or singularize class and member names” já é bem conhecida no Entity Framework, mas ela irá colocar ou não os nomes das entidades no plural, e isto só serve para a lingua inglesa. Já a opção “Create a file per table”, faz com que cada entidade do banco de dados seja um arquivo (classe) separado. Isto é vantajoso em casos de muitas entidades, mas é apenas por uma questão visual e de organização. Exceto casos de extensão da classe e manutenções. Nem vou comentar as demais opções, pois não são relevantes para este exemplo. Clique no botão OK para criar o arquivo BancoContext.cs.

Dn690241.EDF883EF42E6B54BA22887235A2DF2A1(pt-br,MSDN.10).png

Figura 12 – Criar o BancoContext.cs

No Solution Explorer foi criado o arquivo BancoContext.cs, portanto, abra-o para analisarmos os pontos necessários. Logo de cara veja as referências necessárias incluídas para se manipular um arquivo e usar o Linq.

using System.IO;
using System.IO.IsolatedStorage;
using Microsoft.Phone.Data.Linq.Mapping;
using Microsoft.Phone.Data.Linq;

Eu vou inverter um pouco algumas declarações para facilitar o entendimento do código no artigo. Veja que são declaradas três variáveis as quais serão usadas para manipular o arquivo fisicamente Banco.sdf. Observe quão interessante é o formato do Data Source, o qual usa-se o isostore. Isto significa que o o arquivo banco.sdf será criado examente onde a app for instalada, e como todos sabem, uma app Windows Phone roda em uma caixa fechada, tudo o que estiver nesta pasta de instalação, somente a app pode enxergar. Aqui vai uma dica quanto ao File Mode=Read Only. Em alguns casos de testes você pode excluir isto para que o banco não seja somente leitura. E veremos no código como adicionar dados à entidade sem que o Read Only implique em algo. Bom, se pensarmos que todas estas declarações serão propriedades do banco de dados, então são passíveis de mudanças em tempo de execução.

public static string ConnectionString = "Data Source=isostore:/Banco.sdf";
public static string ConnectionStringReadOnly = "Data Source=appdata:/Banco.sdf;File Mode=Read Only;";
public static string FileName = "Banco.sdf";

Já a declaração da classe BancoContext observamos que herda de DataContext, ou seja, tudo o que for relativo ao contexto, criar e excluir banco, entidades e salvar o contexto está embutido nesta fantástica classe. Dê um F12 em DataContext e explore as possiblidades.

public partial class BancoContext : System.Data.Linq.DataContext
{

E se o banco de dados não existir, cria-se automaticamente? Claro que sim, imagine que qualquer pessoa irá baixar e instalar esta app, nada mais justo que criar em tempo de execução! Para isto, há o método CreateIfNotExists, o qual verifica se o banco existe (veja o names.Where que varre todos os recursos da app de acordo com a variável FileName (Banco.sdf)). Caso não exista, é usado o IsolatedStorage para criar (veja o FileMode.Create) com as devidas propriedades através do IsolatedStorageFileStream.

public bool CreateIfNotExists()
{
bool created = false;
if (!this.DatabaseExists())
{
string[] names = this.GetType().Assembly.GetManifestResourceNames();
string name = names.Where(n => n.EndsWith(FileName)).FirstOrDefault();
if (name != null)
{
using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
{
if (resourceStream != null)
{
using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream fileStream = new IsolatedStorageFileStream(FileName, FileMode.Create, myIsolatedStorage))
{
using (BinaryWriter writer = new BinaryWriter(fileStream))
{
long length = resourceStream.Length;
byte[] buffer = new byte[32];
int readCount = 0;
using (BinaryReader reader = new BinaryReader(resourceStream))
{
// read file in chunks in order to reduce memory consumption and increase performance
while (readCount < length)
{
int actual = reader.Read(buffer, 0, buffer.Length);
readCount += actual;
writer.Write(buffer, 0, actual);
}
}
}
}
}
created = true;
}
else
{
this.CreateDatabase();
created = true;
}
}
}
else
{
this.CreateDatabase();
created = true;
}
}
return created;
}

E o construtor da classe? Veja que legal esta parte do construtor onde ele já solicita a string de conexão, que está declarada na variável connectionString. Com isto, assim que a classe BancoContext é invocada, já é atribuída a string de conexão.

public BancoContext(string connectionString) : base(connectionString)

E como declarar as classes que representam as entidades? É simples, o LINQ usa da declaração do System.Data.Linq.Table para identificar a classe a ser usada, neste caso Velejadores. O retorno será o GetTable da mesma classe. O interessante aqui é que o SQL Server Compact Edition não usa o Entity Framework, e sim o Linq To SQL, que é usado apenas para bancos de dados SQL.

public System.Data.Linq.Table<Velejadores> Velejadores
{
get
{
return this.GetTable<Velejadores>();
}
}

Agora é a vez da classe Velejadores. Observe que o Linq mapeia (Mapping) com atributos toda a classe. Toda classe deverá implementar os INotifyPropertyChanging e INotifyPropertyChanged. Tenha em mente o seguinte: os dados serão mostrados na UI no XAML que usa o recurso de Binding, e qualquer alteração na fonte de dados ou UI, os dados devem refletir no outro lado. Para isto, usamos o conceito de notificar a alteração.

[global::System.Data.Linq.Mapping.TableAttribute()]
public partial class Velejadores : INotifyPropertyChanging, INotifyPropertyChanged
{

private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);

Para cada propriedade da classe são definidos os atributos (Storage, DbType, IsPrimaryKey, IsDbGenerated, CanBeNull, etc) e no Set é o local onde é disparado os eventos de notificação de alteração do conteúdo da propriedade. Cabe dizer que os atributos dependem das características de cada campo.

[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Id", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
public int Id
{
get
{
return this._Id;
}
set
{
if ((this._Id != value))
{
this.OnIdChanging(value);
this.SendPropertyChanging();
this._Id = value;
this.SendPropertyChanged("Id");
this.OnIdChanged();
}
}
}

[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Nome", DbType="NChar(100) NOT NULL", CanBeNull=false)]
public string Nome
{
get
{
return this._Nome;
}
set
{
if ((this._Nome != value))
{
this.OnNomeChanging(value);
this.SendPropertyChanging();
this._Nome = value;
this.SendPropertyChanged("Nome");
this.OnNomeChanged();
}
}
}

[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Idade", DbType="Int NOT NULL")]
public int Idade
{
get
{
return this._Idade;
}
set
{
if ((this._Idade != value))
{
this.OnIdadeChanging(value);
this.SendPropertyChanging();
this._Idade = value;
this.SendPropertyChanged("Idade");
this.OnIdadeChanged();
}
}
}

Vou ocultar parte dos códigos gerados por questões de espaço, mas você irá concluir facilmente quando analisar o código do contexto. Até aqui você não verá o arquivo Banco.sdf incluído no projeto, então mostre todos os arquivos do projeto (botão Show All files no Solution Explorer), clique com o botão direito no Banco.sdf e selecione Include in Project. Compile o projeto e certifique-se que está 100% com sucesso.

Dica: configure a propriedade (F4) Build Action para Embedded Resource do arquivo Banco.sdf, pois este arquivo deverá fazer parte dos recursos quando for instalada a app nos devices.

Interface XAML

Como vimos no início do artigo, na figura 2 o projeto foi criado baseado no template de Databound, então, abra a MainPage.xaml, e observe o XAML criado que já aponta o Binding para duas propriedades. Por exemplo, veja um código onde há Binding para as propriedades Nome e Idade, os quais os dados serão lidos do banco de dados e preenchidos aqui no LongListSelector chamado llsDados. A partir de agora você pode manipular os dados via LINQ do jeito que quiser.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <phone:LongListSelector x:Name="llsDados" Margin="0,0,-12,0" 
                        ItemsSource="{Binding Items}">
        <phone:LongListSelector.ItemTemplate>
            <DataTemplate>
                <StackPanel Margin="0,0,0,17">
                    <TextBlock Text="{Binding Nome}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                    <TextBlock Text="{Binding Idade}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                </StackPanel>
            </DataTemplate>
        </phone:LongListSelector.ItemTemplate>
    </phone:LongListSelector>
</Grid>

No C#, veja o código para ler a entidade Velejadores e exibir todos os dados no controle llsDados. Neste código, o BancoContext abre a conexão com o banco de dados declarado em BancoContext.ConnectionString, chama o método CreateIfNotExists para criar o banco, caso não exista, e ao final preenche o llsDados com a entidade de Velejadores. Aqui tem um dado interessante porque se você quiser aplicar qualquer expressão Lambda para filtrar dados, ordenar, etc, é possível.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
     //if (!App.ViewModel.IsDataLoaded)
     //{
     //    App.ViewModel.LoadData();
     //}
     using (var ctx = new BancoContext(BancoContext.ConnectionString))
     {
          ctx.CreateIfNotExists();
          llsDados.ItemsSource = ctx.Velejadores.ToList();
     }
}

Pronto, o banco de dados já está incluído no projeto e você tem acesso a todos os dados para fazer o CRUD completo. Execute a aplicação e veja o resultado, conforme a figura 13.

Dn690241.9DC1EF4DBE518285BD72BA9B9A0B35E1(pt-br,MSDN.10).png

Figura 13 – Dados oriundos do banco de dados

Caso queira um código para fazer uns inserts, aqui está um exemplo:

var lista = new List<Velejadores>
{ 
    new Velejadores { Nome="Renato", Idade=41},
    new Velejadores { Nome="CD", Idade=40},
    new Velejadores { Nome="Fiore", Idade=35}
};

ctx.Velejadores.InsertAllOnSubmit(lista);

ctx.SubmitChanges();


Funcionalidades Interessantes do SQL Server Compact Toolbox

Já que estamos usando a Toolbox, quero destacar algumas funcionalidades que estão prontas e nos ajudará como nunca no ganho de tempo. Na figura 14 veja a quantidade de tipos de scripts que podem ser gerados, sendo Schema, Schema e Dados, tanto para o SQL Azure, SQLite ou BLOBs, script de dados, dados para o SQL Server e Diff (comparar bancos). Ou seja, qualquer tipo de script que você pensar, há disponível.

Dn690241.7CB95B043B56698DC8C8F75A30A36FAA(pt-br,MSDN.10).png

Figura 14 – Tipos de scripts para gerar


Ainda na figura 14 temos:

  • Open SQL Editor – abre o editor do T-SQL dentro do VS 2013.
  • Build Table – abre a janela para você criar uma tabela.
  • Maintenance – manutenções do banco, por exemplo, Shrink (exclui páginas em branco), Compact (compactar o banco), Verify (verifica a integridade do banco), Repair (3 opções de reparar o banco).
  • Migrate to SQL Server (LocalDB/Express) – faz a migração do SQL Compact para o LocalDB ou SQL Express.
  • Upgrade to version 4.0 – caso esteja usando a versão 3.5, migra para o 4.0.
  • Create Database Graph (DGML) – cria um arquivo com o gráfico das entidades do banco.
  • Create Database Documentation – cria um arquivo HTML com a estrutura do banco.
  • Generate Code (EF/LINQ to SQL/SyncFX) – gera os códigos C# (classes) para o EDMX, os DataContext para o LINQ to SQL ou Windows Phone, códigos para usar o Sync Framework e as ferramentas do mesmo.
  • Edit description – permite editar a descrição do banco.
  • Database Information – exibe todas as informações do banco (locale identifier: 1033, encryption mode:, case sensitive: False, Database: C:\DatabaseWP8\Banco.sdf, ServerVersion: 3.5.8080.0, DatabaseSize: 36 KB, SpaceAvailable: 3,999 GB, Created: 02/05/2014 13:11, User Table information: Number of tables: 1, Velejadores: 8 row(s)).
  • Copy Database File – copia o banco na memória.
  • Remove Connection – remove a conexão do banco da Toolbox.

E, para finalizar, veja as opções que temos para a manipulação das tabelas, conforme a figura 15. Todas as opções são conhecidas do leitor e quero ressaltar que sem esta ferramenta da Toolbox era muito braçal a manipulação destas opções. Agora está tudo disponível diretamenta no VS sem nenhum outro recurso extra para se manusear um arquivo .sdf.

Dn690241.634B7A909153F59D1AB69895ADF5DC60(pt-br,MSDN.10).png

Figura 15 – Opções de manutenção de dados da tabela

Conclusão

Eu sempre fui um disseminador das facilidades das ferramentas que o Visual Studio nos oferece, e quando a Microsoft lançou esta Toolbox do SQL Server Compact pensei o quão os desenvolvedores precisam saber como explorar as opções. E, integrar um banco de dados com o Windows Phone 8/8.1 e o Windows 8/8.1 ficou muito fácil, ainda mais com o uso de Linq e Lambda.

Agradeço a oportunidade de poder compartilhar o conhecimento com todos. Qualquer dúvida e preparação de times de desenvolvimento, por favor me contate.

Sobre o Autor

Renato Haddad (rehaddad@msn.comwww.renatohaddad.com ) é MVP, MCPD e MCTS, palestrante em eventos da Microsoft em diversos países, ministra treinamentos focados em produtividade com o VS.NET 2012/2013, ASP.NET 4/5, ASP.NET MVC, Entity Framework, Reporting Services, Windows Phone e Windows 8. Visite o blog http://weblogs.asp.net/renatohaddad.

Mostrar: