WP7 – Mango Beta – Base de donnée locale

7 minutes read

Cet article utilise la version Beta du SDK de WP7 v7.1 alias Mango. Aussi au moment où vous lirez ces lignes, le contenu de cet article pourra être obsolète.

Parmi les nombreuses nouveautés qu’apporte Mango, il en est une qui m’intéresse tout particulièrement et qui est l’ajout d’un base de donnée locale au téléphone. En effet, pour ceux qui ne sont pas au courant, j’ai développé un petit jeu d’Othello sur WP7 du nom de Yao. Ce jeu sauvegarde actuellement les scores des parties dans l’Isolated Storage. Cela fonctionne mais j’aurai aimé avoir quelque chose d’un peu plus structuré. Aussi, à des fins de tests j’ai regardé un peu comment cela fonctionne.

La base de donnée sur WP7 est basée sur SQL CE et on peut y accéder en utilisant du Linq-To-SQL. Oui oui, vous avez bien lu, Linq-To-SQL. Ceci dit ici point de fichier dbml, le modèle ici est généré à la main mais on verra ça plus tard.

Le contexte

Ici le contexte sera : pouvoir ajouter, modifier et supprimer des scores d’Othello dans la base de donnée locale du téléphone.

Préparation du modèle

Afin de pouvoir utiliser la base de donnée interne au téléphone il faut d’abord rajouter une référence à l’assembly System.Data.Linq.

Une fois ceci fait on va pouvoir ajouter commencer à créer le modèle.

Voici la classe Score que nous souhaitons persister en base :

[Table]
public class Score
{
    [Column(
        IsPrimaryKey = true,
        IsDbGenerated = true,
        DbType = "INT NOT NULL Identity",
        CanBeNull = false,
        AutoSync = AutoSync.OnInsert)]
    public int Id { get; set; }

    [Column]
    public int White { get; set; }

    [Column]
    public int Black { get; set; }
}

Vous remarquez deux attributs : Table et Column. Leurs noms sont assez explicites et permettent d’indiquer que la classe Score est une table de la base et qu’elle contient trois colonnes : Id, White et Black.

On noteras cependant que l’attribut Column de la propriété Id est plus détaillé que les autres. Voici les significations des différentes propriétés de l’attribut Column pour la propriété Id :

  • IsPrimaryKey à True indique que Id est la clef primaire de la table Score
  • IsDbGenerated à True indique que la valeur de la colonne est générée par la base
  • DbType indique le type SQL de la colonne tel qu’il serait défini dans une base SQL CE standard, ici entier non-nullable et de type identité
  • CanBeNull à False indique que la colonne ne peux avoir de valeur nulle
  • AutoSync indique quand doivent être synchronisée les données entre la base SQL et le contexte de donnée actuel, ici la synchronisation se fait à l’insertion

On va ensuite créer un classe que représentera notre contexte de donnée, notre DbContext en fait. Voici la classe en question :

public class Model : System.Data.Linq.DataContext
{
    public Model(string connectionString)
        : base(connectionString)
    {
    }

    public System.Data.Linq.Table<Score> Scores;
}

La classe Model représente la base de donnée dans laquelle on a une table des scores. Ici on ne fait rien d’autre de spécial.

Création de la base de donnée

Lorsque l’application se lance pour la première fois, la base de donnée n’est pas encore créée. Il faudra donc la créer à ce moment là.

Pour ce faire, rendez-vous dans le fichier App.xaml.cs. Dans ce fichier nous allons rajouter la chaine de connexion à utiliser pour notre application :

public static string ConnectionString = "Data Source=isostore:/MangoLocalDatabase.sdf";

Ensuite dans la méthode InitializePhoneApplication on va rajouter le code suivant :

using (Model db = new Model(ConnectionString))
{
    if (db.DatabaseExists() == false)
        db.CreateDatabase();
}

C’est tout ce que l’on a à faire pour créer la base de donnée lorsque celle-ci n’existe pas.

Affichage

Voici le code XAML de la MainPage de l’application :

<StackPanel Background="Transparent">
    <ListBox x:Name="listBox">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal" Margin="15">
                    <TextBlock Text="Black : " />
                    <TextBox Text="{Binding Black, Mode=TwoWay}" Width="100" InputScope="Number" />
                    <TextBlock Text="White : " />
                    <TextBox Text="{Binding White, Mode=TwoWay}" Width="100" InputScope="Number" />
                    <Button x:Name="deleteBtn">Delete</Button>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <StackPanel Orientation="Horizontal" Margin="15">
        <TextBlock Text="Black : " HorizontalAlignment="Center"  />
        <TextBox x:Name="tbBlack" Width="100" InputScope="Number" />
        <TextBlock Text="White : " HorizontalAlignment="Center" />
        <TextBox x:Name="tbWhite" Width="100" InputScope="Number" />
    </StackPanel>
    <Button x:Name="addBtn">Add</Button>
</StackPanel>

Affichage d’éléments venant de la base

Nous allons commencer par ajouter le code nous permettant d’afficher des scores provenant de la base. Pour cela on va aller modifier le code-behind de la page principale pour qu’il ressemble à celui-ci :

public partial class MainPage
{
    private readonly Model _model;
    private readonly ObservableCollection<Score> _scores;

    public MainPage()
    {
        InitializeComponent();

        _model = new Model(App.ConnectionString);
        _scores = new ObservableCollection<Score>(_model.Scores);

        listBox.ItemsSource = _scores;
    }

    protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
        base.OnNavigatedFrom(e);

        _model.Dispose();
    }
}

On a donc créé deux champs, un pour le modèle de donnée et une collection observable de scores. Dans le constructeur de la vue on créer une connexion à la base de donnée et on initialise la collection observable des scores avec la table Score, ensuite on lie la ListBox avec la collection observable. Enfin on n’oublie pas de fermer la connexion à la base lorsque l’on navigue hors de la vue.

Tout cela est bien utile mais pour l’instant on a aucun élément à afficher. On va donc en rajouter.

Ajout d’éléments dans la base

Pour cela on va gérer l’évènement Click sur le bouton addBtn. Voici le code du gestionnaire d’évènement :

private void addBtn_Click(object sender, RoutedEventArgs e)
{
    int black;
    int white;

    if (int.TryParse(tbBlack.Text, out black))
    {
        if (int.TryParse(tbWhite.Text, out white))
        {
            Score score = new Score { Black = black, White = white };

            _model.Scores.InsertOnSubmit(score);
            _scores.Add(score);

            tbBlack.ClearValue(TextBox.TextProperty);
            tbWhite.ClearValue(TextBox.TextProperty);
        }
    }
}

Ici on récupère la valeur des deux TextBox et on construit une instance de la classe Score. Cette instance est d’abord ajouté dans le modèle où elle se verras générer un identifiant automatiquement et ensuite on peux ajouter cette même instance dans la collection observable pour que l’affichage se mette à jour. On nettoie ensuite les valeur des TextBox.

A ce stade il faut noter que l’objet a été ajouté au modèle mais n’a pas encore été persisté en base (méthode InsertOnSubmit). Pour persister les objets il faut aller modifier la méthode OnNavigatedFrom et utiliser SaveChanges sur le modèle :

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);

    _model.SubmitChanges();
    _model.Dispose();
}

Maintenant on peux ajouter des scores, mais il se peux que l’on veuille les supprimer aussi s’ils n’ont plus de raison d’être.

Suppression d’éléments de la base

Pour cela on va gérer l’évènement Click sur le bouton deleteBtn. Voici le code du gestionnaire d’évènement :

private void deleteBtn_Click(object sender, RoutedEventArgs e)
{
    Button btn = sender as Button;

    if (btn == null)
        return;

    Score score = btn.DataContext as Score;
    _model.Scores.DeleteOnSubmit(score);
    _scores.Remove(score);
}

Comme notre bouton est dans un DataTemplate d’une ListBox on récupère le Score associé au bouton via son DataContext. Ensuite, en utilisant la méthode DeleteOnSubmit de la table Score on marque l’objet en suppression et on le supprime de la collection observable par la même occasion.

Dans l’état actuel des choses on est capable de faire un Select, un Insert et un Delete. Mais pour l’instant pas d’Update.

Mise à jour d’enregistrements

Afin que le moteur de base de donnée puisse savoir si une classe a été modifiée ou non il suffit de faire implémenter cette classe de l’interface INotifyPropertyChanged mais il est recommandé de la faire implémenter en plus l’interface INotifyPropertyChanging pour des raisons de performance. Une fois ceci fait, tout à été fait.

Voyons le nouveau code source de la classe Scores :

[Table]
public class Score : INotifyPropertyChanged, INotifyPropertyChanging
{
    private int _id;
    private int _white;
    private int _black;

    public event PropertyChangedEventHandler PropertyChanged;
    public event PropertyChangingEventHandler PropertyChanging;

    [Column(
        IsPrimaryKey = true,
        IsDbGenerated = true,
        DbType = "INT NOT NULL Identity",
        CanBeNull = false,
        AutoSync = AutoSync.OnInsert)]
    public int Id
    {
        get
        {
            return _id;
        }

        set
        {
            if (_id != value)
            {
                NotifyPropertyChanging("Id");
                _id = value;
                NotifyPropertyChanged("Id");
            }
        }
    }

    [Column]
    public int White
    {
        get
        {
            return _white;
        }

        set
        {
            if (_white != value)
            {
                NotifyPropertyChanging("White");
                _white = value;
                NotifyPropertyChanged("White");
            }
        }
    }

    [Column]
    public int Black
    {
        get
        {
            return _black;
        }

        set
        {
            if (_black != value)
            {
                NotifyPropertyChanging("Black");
                _black = value;
                NotifyPropertyChanging("Black");
            }
        }
    }

    private void NotifyPropertyChanging(string propertyName)
    {
        if (PropertyChanging != null)
            PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
    }

    private void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Conclusion

La gestion de la base de donnée dans WP7 est somme toute assez simple. Il est cependant étrange que Linq-To-SQL ait été choisi alors que celui-ci est remplacé par Entity Framework. De plus, l’approche ici s’apparente plus a de l’Entity Framework code-first qu’à du Linq-To-Sql puisque la configuration se fait entièrement par code.

A noter que le framework est encore en version bêta et que donc les choses peuvent être amenée à évoluer.

Comme d’habitude vous retrouverez les sources de l’application sur mon skydrive.

Updated:

Leave a Comment