2

Feb

Come copiare un database SQL CE nello storage di un’applicazione Windows Phone

Se avete letto il mio articolo pubblicato su ASPItalia dedicato all’uso di SQL CE in Windows Phone Mango, saprete che ci sono due modalità di accesso ad un database relazionale in Windows Phone:

  • Code first: il database viene creato al primo avvio dell’applicazione nello storage in base ad una serie di convenzioni (definite nelle entità). Questa modalità è indicata quando i dati da memorizzare  vengono generati all’interno della nostra applicazione e non abbiamo bisogno di dati precaricati (ad esempio, un’applicazione per archiviare i nostri film), oppure quando questi dati vengono scaricati da una sorgente esterna (ad esempio, un servizio web).
  • Read only: alternativamente, possiamo preventivamente creare e popolare un database SQL CE e poi includere il file all’interno del nostro progetto. In questa modalità potremo accedere al database solamente in sola lettura: questa opzione è indicata quando dobbiamo sviluppare un’applicazione che faccia uso di dati precaricati, i quali non verranno modificati durante il ciclo di vita dell’applicazione (ad esempio, un’applicazione per consultare ricette).

In alcuni casi però si ha la necessità di avere a disposizione i vantaggi di entrambi le modalità: un database già precaricato con dei dati, che però dovranno essere modificati durante l’utilizzo da parte degli utenti. La soluzione in questo è caso è, in teoria, molto semplice: copiare il database dal progetto all’isolated storage al primo avvio dell’applicazione. In pratica, ci viene in aiuto uno strumento di cui vi ho già parlato nell’articolo pubblicato su ASPItalia: SQL Server Compact Toolbox.

Tale utility si integra con Visual Studio e offre una serie di feature utili per la gestione di database SQL CE: quella che più ci interessa è una nuova opzione, aggiunta nelle ultime versioni, che permette, partendo da un database già esistente, di generare in automatico le entità e il DataContext, necessari all’interno della nostra applicazione per operare con il database. Non sto qui a spiegarvi come usare questo tool, dato che l’ho già spiegato per filo e per segno nell’articolo di ASPItalia.

Quello su cui vorrei soffermarmi è il fatto che il DataContext che viene generato dal tool include un metodo chiamato CreateIfNotExists, da utilizzare all’avvio dell’applicazione: tale metodo non fa altro che verificare l’esistenza o meno del database nello storage e, in caso negativo, crearlo.

Quello che però è in grado di fare il metodo CreateIfNotExists è anche copiare un database incluso nel nostro progetto nello storage della nostra applicazione: per utilizzarlo, vi basta includere il file del vostro database (caratterizzato dall’estensione sdf) nel progetto e, dalle proprietà, impostare la Build Action su EmbeddedResource.

Il gioco è fatto: all’avvio dell’applicazione vi basterà chiamare il metodo CreateIfNotExists (ad esempio, nell’evento Application_Launching esposto dall’App.xaml.cs) e il database da voi importato verrà copiato nello storage, così da essere accessibile anche in scrittura. Ovviamente, tutto questo verrà eseguito solo al primo avvio: negli avvii successivi il database sarà già presente nello storage e quindi non verrà copiato.

Occhio alla dimensione!

Tenete d’occhio la dimensione del vostro database: maggiore è lo spazio occupato, maggiore sarà il tempo necessario per copiarlo nello storage. E’ buona norma perciò prevedere un’animazione di caricamento (ad esempio, utilizzando una ProgressBar) all’avvio, nel caso il tempo di copia impieghi più di 2 o 3 secondi (vi ricordo che il tempo di caricamento dell’applicazione, quello in cui viene visualizzato lo splash screen, non deve superare i 5 secondi).

E se il database è memorizzato in un altro progetto?

Può capitare la necessità di avere il DataContext definito all’interno di un progetto e il file del database memorizzato invece in un altro: in questo caso, il metodo CreateIfNotExists non funzionerà, dato che questi va a cercare il database tramite reflection all’interno dello stesso assembly.

Il trucco in questo caso è quello di impostare la Build Action del database su Content e cambiare le righe di codice del metodo CreateIfNotExists che vanno a caricare il file all’interno di uno stream nel seguente modo:

public bool CreateIfNotExists()
      {
      bool created = false;
      using (var db = new DbContext(DbContext.ConnectionString))
      {
      if (!db.DatabaseExists())
      {
      //string[] names = this.GetType().Assembly.GetManifestResourceNames();
      //string name = names.Where(n => n.EndsWith(FileName)).FirstOrDefault();
      StreamResourceInfo stream = Application.GetResourceStream(new Uri("Database/Recipes.sdf", UriKind.Relative));
      if (stream != null)
      {
      using (Stream resourceStream = stream.Stream)
      {
      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
      {
      db.CreateDatabase();
      created = true;
      }
      }
      }
      else
      {
      db.CreateDatabase();
      created = true;
      }
      }
      }
      return created;
      }

Qualche nota:

  • Viene usato il metodo Application.GetResourcesStream per caricare una risorsa dal progetto al posto di quello precedente. Questo metodo va infatti a cercare tra tutti i file inclusi nello XAP, indipendentemente dal progetto di appartenenza.
  • Il parametro passato al metodo GetResourcesStream è di tipo Uri è rappresenta il percorso del file del database all’interno del nostro progetto, partendo dalla root. Nell’esempio, il file si chiama Recipes.sdf ed è contenuto nella cartella Database. Ovviamente, dovete cambiare questo parametro in base al percorso del database nella vostra applicazione.
  • Abbiamo cambiato la condizione da rispettare affinchè il database venga copiato, ovvero l’esistenza del database sotto forma di risorsa all’interno del progetto (se il database non esiste, il metodo GetResourcesStream ritornerà null).
by Il blog di Matteo Pagani on 2/2/2012
Post archive