1 commentaires dimanche 17 mai 2009

Disons que vous avez une base de données contenant une table User, contenant les informations des utilisateurs de votre site. Vous voulez probablement représenter un ensembles d'options sous la forme de booléens (ENUM ou BOOLEAN sous MySQL, bit sous SQL Server, etc.). Le problème avec cette approche, est que si vous voulez ajouter des options additionnelles, vous devrez modifier la structure de la base de données. Et je ne sais pas pour vous, mais je ne suis pas très chaud à l'idée de faire des ALTER TABLE sur une BD live. Vous pouvez donc ajouter une table d'options, et une table de liaison reliant une option à un utilisateur (avec probablement la valeur de cette option). Mais c'est plutôt lourd. Une solution au problème serait donc d'utiliser une colonne, d'un type supportant un entier 32 bits (INT sous MySQL, int sous SQL Server, etc.), de votre table User, pour stocker des flags. À coup de manipulation de bits, on pourrait donc avoir 32 options différentes avec un seul int. Sauf que les manipulation de bits, c'est complexe. Et c'est l'enfer à maintenir.

C'est alors que j'ai découvert le struct BitVector32, du namespace System.Collections.Specialized. Il permet de représenter un int (System.Int32) comme un ensemble de booléens. Il a également d'autre utilisations, mais je ne les couvrirai pas ici. Pour plus d'informations, consultez MSDN.

Commençons par définir une énumération contenant les divers flags qui s'appliqueront à notre utilisateur:


[Flags]
public enum UserFlags
{
IsActive = 1,
IsBanned = 2,
ShowEmail = 4
}
Il est important que chaque élément de l'énumération ait pour valeur une puissance de 2. De même, l'attribut Flags n'est pas obligatoire, mais permettrait à notre énumération d'être utilisée comme flags dans un contexte "à la C", avec les valeurs séparées par des pipes (|).

Puis, créons une petite classe User. Elle pourrait être mappée à l'aide d'un ORM comme NHibernate ou SubSonic.

public class User
{
public string Username { get; private set; }
public string Password { get; private set; }
public string Email { get; private set; }
private BitVector32 _flags;

public User(string username, string password, string email)
{
Username = username;
Password = password;
Email = email;
_flags = new BitVector32();
}
}
Puis, on ajoute des propriétés à notre classe User:

public bool IsActive
{
get { return _flags[(int)UserFlags.IsActive]; }
set { _flags[(int)UserFlags.IsActive] = value; }
}

public bool IsBanned
{
get { return _flags[(int)UserFlags.IsBanned]; }
set { _flags[(int)UserFlags.IsBanned] = value; }
}

public bool ShowEmail
{
get { return _flags[(int)UserFlags.ShowEmail]; }
set { _flags[(int)UserFlags.ShowEmail] = value; }
}
Voilà. L'usage est entièrement transparent; du côté du code client, on a l'impression de manipuler des booléens, alors qu'en réalité, tout se passe dans un int.

On peut également, pour des fins de tests, produire une propriété RawFlags qui retournera la valeur entière de notre BitVector32:

public int RawFlags
{
get
{
return _flags.Data;
}
}
Lorsqu'il viendra le temps d'enregistrer les flags dans la BD, il suffira alors de prendre la valeur contenue dans la propriété Data du vecteur. On peut effectuer l'opération inverse en utilisant le constructeur de BitVector32 qui prend un int.

Voilà!