Précédemment, nous avons vu comment utiliser ADO.NET pour lire un fichier texte au format CSV. Dans cet article, nous allons aller plus loin : il s’agit de créer une classe générique permettant de manipuler des bases de données au format texte facilement. Nous l’appellerons « TextFileDataAccess ».
Les colonnes contenues dans le fichier texte seront associées à une propriété publique d’une classe.
Ainsi chaque fichier texte CSV aura une classe correspondante.
En nous basant sur le fichier csv de l’article précédent, nous obtenons donc la classe « User » :
class User
{
public Int32 IdUser { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Mail { get; set; }
public DateTime BirthDate { get; set; }
}
Pour rappel, le fichier CSV :
IdUser,FirstName,LastName,Mail,BirthDate
0,John,Dubois,jdubois@alexandrefromage.fr,06/11/1989
L’interface ITextFileDataAccess
La classe« TextFileDataAccess » implémentera l’interface « ITextFileDataAccess ».Cette interface expose les quatre méthodes du CRUD :
interface ITextFileDataAccess<T> where T : class
{
//CRUD
void Insert(T objet); //Create
List<T> List(); //Read
void Delete(T objet); //Update
void Update(T objet); //Delete
}
Ici le type « T » correspondrait par exemple à la classe « User ».
La classe « TextFileDataAccess »
Voici le code de cette classe, seules « Insert» et « List » ont été implémentées:
class TextFileDataAcces<T> : ITextFileDataAccess<T> where T:class
{
public TextFileDataAcces(string connectionString, string fileName)
{
this.connectionString = connectionString;
this.fileName = fileName;
//HeadersEnable renvoie true si les headers sont utilisés
this.headersEnable = this.HeadersEnable();
}
private List<T> ParseFile()
{
//On initialise la liste
List<T> list = new List<T>();
//On initialise la connection
OleDbConnection connection = new OleDbConnection()
{
ConnectionString = this.connectionString
};
OleDbCommand command = new OleDbCommand();
command.CommandType = System.Data.CommandType.Text;
command.CommandText = "SELECT * FROM " + this.fileName;
command.Connection = connection;
//Utilisation de la réflexion pour récupérer les propriétés publiques de la classe T
Type type = typeof(T);
PropertyInfo[] properties = type.GetProperties();
OleDbDataReader reader;
int increment;
using (connection)
{
connection.Open();
using (reader = command.ExecuteReader())
{
while (reader.Read())
{
//On utilise la réflexion pour créer une instance d'objet de type T
//La classe T doit contenir un constructeur PUBLIC SANS PARAMETRES
T objet = (T)typeof(T).GetConstructor(Type.EmptyTypes).Invoke(null);
//
increment = 0;
//On utilise la réflexion pour parcourir toutes les propriétés publiques de T
foreach (PropertyInfo property in type.GetProperties())
{
//on utilise la réflexion pour accéder à une propriété de l'objet de type T
//Si la première du fichier contient les headers
//Chaque nom de propriété publique de l'objet de type T doit correspondre à
//un header --> IndexOutOfRangeException
if(this.headersEnable)
property.SetValue(objet, reader[property.Name], null);
//Si il n'y-a pas de headers
//Le nombre de propriétés publiques de l'objet de type T doit correspondre au
//nombre de colonnes --> IndexOutOfRangeException
else
property.SetValue(objet, reader[increment], null);
increment++;
}
list.Add(objet);
}
}
}
return list;
}
/// <summary>
/// Analyse la chaine de connexion pour savoir si les headers sont utilisés.
/// </summary>
/// <returns>True si la première ligne du fichier texte contient les headers, False sinon</returns>
private bool HeadersEnable()
{
//On utilise le caractère ';' pour découper la chaine de connexion.
//';' sépare tous les paramètres
string[] connParams = this.connectionString.ToLower().Split(';');
foreach (string param in connParams)
{
//Si le paramètre courant est HDR, on regarde sa valeur.
if(param.Contains("hdr"))
return param.Contains("yes")== true? true : false;
}
//La propriété HDR n'a pas été trouvée.
throw new Exception("HDR property not set!");
}
/// <summary>
/// Parse le fichier et renvoie la liste d'objets correspondant.
/// </summary>
/// <returns>Liste d'objets de type T</returns>
public List<T> List()
{
//On parse le fichier et on renvoie la liste crée.
return this.ParseFile();
}
/// <summary>
/// Insère un objet de type T dans le fichier
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="objet"></param>
public void Insert(T objet)
{
//On génère la requete a exécuter.
//Utilisation de la réflexion pour récupérer les propriétés publiques de la classe T
Type type = typeof(T);
PropertyInfo[] properties = type.GetProperties();
//On initialise la connection
OleDbConnection connection = new OleDbConnection()
{
ConnectionString = this.connectionString
};
//Requête sql
StringBuilder request = new StringBuilder("INSERT INTO " + this.fileName);
StringBuilder nameRequest = new StringBuilder("(");
StringBuilder valuesRequest = new StringBuilder(" VALUES(");
//Commande
OleDbCommand command = new OleDbCommand();
foreach (PropertyInfo property in properties)
{
//On crée un paramètres en utilisant:
//le nom de la propriété
//La valeur de la propriété dans l'objet à insérer
OleDbParameter dbParam = new OleDbParameter("@" + property.Name, property.GetValue(objet, null));
command.Parameters.Add(dbParam);
valuesRequest.Append("@" + property.Name + ",");
//Si on utilise les headers, on utilise le nom des propriétés publiques pour
//déduire les noms de colonne
if (this.headersEnable)
nameRequest.Append(property.Name + ",");
}
//Noms
if (this.headersEnable)
{
//On supprime la dernière virgule
nameRequest.Remove(nameRequest.Length - 1, 1);
//On ajoute une )
nameRequest.Append(")");
//On concatène les éléments de la requête
request.Append(nameRequest);
}
//Valeurs
//On supprime la dernière virgule
valuesRequest.Remove(valuesRequest.Length - 1, 1);
//On ajoute une )
valuesRequest.Append(")");
//On concatène les éléments de la requête
request.Append(valuesRequest);
//
command.CommandType = System.Data.CommandType.Text;
command.CommandText = request.ToString();
command.Connection = connection;
using (connection)
{
connection.Open();
//On exécute la requête
command.ExecuteNonQuery();
}
}
public void Delete(T objet)
{
throw new NotImplementedException();
}
public void Update(T objet)
{
throw new NotImplementedException();
}
private string connectionString;
private string fileName;
private bool headersEnable;
}
Cette classe supporte les fichiers texte utilisant et n’utilisant pas les headers. La chaine de connexion est analysée dans le constructeur pour détecter si les headers sont utilisés.
Exemple d’utilisation
Pour utiliser « TextFileDataAccess » :
//Avec headers
string connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
"C:/Users/Alexandre/Desktop/;Extended Properties=\"text;HDR=Yes;FMT=Delimited\";";
string fileName = "testHeaders.csv";
//Sans headers
//string connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
// "C:/Users/Alexandre/Desktop/;Extended Properties=\"text;HDR=No;FMT=Delimited\";";
//string fileName = "testNoHeaders.csv";
TextFileDataAcces<User> daoUsers = new TextFileDataAcces<User>(connectionString, fileName);
List<User> users = daoUsers.List();
User user = new User()
{
IdUser = 51289,
FirstName = "Alexandre",
LastName = "Fromage",
Mail = "afromage@alexandrefromage.com",
BirthDate = new DateTime(1989,11,06)
};
daoUsers.Insert(user);
Et voilà !
Vous pouvez maintenant utiliser très facilement des fichiers texte pour y stocker vos données.
Fichiers sources: ITextFileDataAccess, TextFileDataAccess.
Article original.
09f9a77a-a5a8-4b1c-9f43-185af341a389|0|.0
ADO.NET, C#
c#, ado, tutoriel, génériques, réflexion