[Win8.1, WP8.1] C#, allez plus loin avec SQLite
Dans mon dernier article ([Win8.1, WP8.1] Stocker ses données avec SQLite en C#), je vous ai expliqué comment utiliser SQLite pour réaliser des opérations simples (CRUD) notamment grâce au wrapper sqlite-net. Parfois, les basiques ne suffisent pas pour certains besoins bien précis que je vais vous exposer.
Ne plus utiliser les attributs SQLite
Récemment dans un projet client Windows 8.1, nous avons utilisé SQLite pour stocker en local des données. Jusque-là aucun soucis, cependant nous avions également un autre projet (une web api) qui allait nous fournir les données à stocker dans la base SQLite. Notamment pour des raisons de maintenabilité, nous voulions utiliser les mêmes classes dans nos deux projets pour éviter d’avoir des effets de bords si jamais nous étions amenés à modifier les modèles au cours de l’avancement du projet. Nous avons donc choisi de créer les classes dans un projet (portable class librairies W8.1) et de les référencer sous forme de lien dans le second projet (portable class librairies) et c’est bien là que la syntaxe sqlite-net vu précédemment pose problème avec les attributs SQLite !
Je vais reprendre mes deux classes Customer et Purchase, qui ont les attributs PrimaryKey et AutoIncrement :
public class Customer { [PrimaryKey, AutoIncrement] public int Id { get; set; } public string DisplayName { get; set; } public DateTime DateOfBirth { get; set; } } public class Purchase { [PrimaryKey, AutoIncrement] public int Id { get; set; } public int CustomerId { get; set; } public DateTime PurchaseDate { get; set; } }
Le problème avec ce code, c’est que dans mon second projet (la web api), je vais devoir référencer sqlite et sqlite-net pour me permettre de compiler et donc d’utiliser ces classes. Cependant, ce n’est pas du tout le bon moyen de résoudre ce problème, puisque dans ma web api je n’ai nullement besoin de sqlite. Alors comment faire pour se soustraire de ces attributs dans la web api mais continuer à utiliser mes classes avec sqlite-net ?
Nous allons utiliser les CreateFlags définis par SQLite, les voici !
[Flags] public enum CreateFlags { None = 0, ImplicitPK = 1, // create a primary key for field called 'Id' (Orm.ImplicitPkName) ImplicitIndex = 2, // create an index for fields ending in 'Id' (Orm.ImplicitIndexSuffix) AllImplicit = 3, // do both above AutoIncPK = 4 // force PK field to be auto inc }
Ces flags sont définis dans le fichier “SQLite.cs” fournit par sqlite-net.
SQLite peut donc se soustraire des attributs et détecter de façon automatique le rôle des champs, si l’on respecte certaines conventions. Pour remplacer les attributs “PrimaryKey” et “AutoIncrement” de mes classes “Customer” et “Purchase”, je vais faire appel à la méthode de création des tables en lui passant les flags que je souhaite qu’il utilise :
connexion.CreateTable<Customer>(CreateFlags.ImplicitPK | CreateFlags.AutoIncPK); connexion.CreateTable<Purchase>(CreateFlags.ImplicitPK | CreateFlags.AutoIncPK);
En utilisant ces deux flags, SQLite va me détecter ma clé primaire et me l’auto-incrémenter. Il se base alors sur des constantes définies dans la classe Orm (présente dans le fichier “SQLite.cs”), par défaut, la valeur “ImplicitPkName” est égale à “Id”. C’est dans la class “Column” et plus précisément dans son constructeur, qu’est utilisée cette valeur :
IsPK = Orm.IsPK(prop) || (((createFlags & CreateFlags.ImplicitPK) == CreateFlags.ImplicitPK) && string.Compare (prop.Name, Orm.ImplicitPkName, StringComparison.OrdinalIgnoreCase) == 0);
On voit alors que lorsque les flags sont définis, un string.Compare est effectué, il faut donc que notre nom de propriété soit “Id”. Dans mon cas cela va fonctionner mais on pourrait tout à fait modifier la valeur de “ImplicitPkName” et le string.Compare, pour par exemple avoir comme nom de clé primaire MaClasse_Id.
Il reste une dernière chose à faire, si comme moi vous utilisez la classe Async et que vous avez essayé d’y adapter mes explications, vous aurez remarqué qu’elle n’utilise pas les flags. Il faut alors modifier quelque peu la méthode de création de table pour qu’elle les accepte :
public Task<CreateTablesResult> CreateTableAsync<T>(CreateFlags createFlags = CreateFlags.None) where T : new () { return CreateTablesAsync (createFlags, typeof (T)); } // Effectuer les mêmes modifications pour les différentes implémentations de CreateTablesAsync public Task<CreateTablesResult> CreateTablesAsync<T, T2, T3, T4, T5>(CreateFlags createFlags = CreateFlags.None) where T : new () where T2 : new () where T3 : new () where T4 : new () where T5 : new () { return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4), typeof (T5)); } public Task<CreateTablesResult> CreateTablesAsync (CreateFlags createFlags, params Type[] types) { return Task.Factory.StartNew (() => { CreateTablesResult result = new CreateTablesResult (); var conn = GetConnection (); using (conn.Lock ()) { foreach (Type type in types) { int aResult = conn.CreateTable (type, createFlags); result.Results[type] = aResult; } } return result; }); }
C’est tout ! Ainsi, grâce à ces quelques modifications et adaptations de notre ancien code, nous pouvons nous dédouaner de l’utilisation des attributs SQLite, et nos classes modèles sont “propres”.
Commentaires