Notes d'étude sur l'enregistrement des opérations, des journaux et des alarmes, avec requêtes via SQLite.
Analyse : que faut-il enregistrer ? Catégories
- Enregistrements d'opérations : modification de paramètres, commandes manuelles (boutons de contrôle), connexion, déconnexion.
- Informations de journal : liste AddLog de la fenêtre de supervision, également écrite en base.
- Informations d'alarme : AddAlarm affiché dans la fenêtre de supervision, également écrit en base ; alarmes de position non atteinte, alarmes de servomoteur.
1. Interafce utilisateur des alarmes
(L'interface utilisateur de l'écran de requête est décrite dans la section 5.)
2. Classe d'entité
namespace ModelesMagasinAutomatique
{
/// <summary>
/// Classe de journal système
/// </summary>
public class JournalSysteme
{
public JournalSysteme() { }
public JournalSysteme(string message, string etatAlarme, TypeJournal type, string utilisateur)
{
Horodatage = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
Message = message;
EtatAlarme = etatAlarme;
Type = type;
Utilisateur = utilisateur;
}
public string Horodatage { get; set; }
public string Message { get; set; }
/// <summary>
/// État de l'alarme : déclenchement ou acquittement
/// </summary>
public string EtatAlarme { get; set; }
/// <summary>
/// Type de journal (énumération)
/// </summary>
public TypeJournal Type { get; set; }
public string Utilisateur { get; set; }
}
/// <summary>
/// Types de journaux
/// </summary>
public enum TypeJournal
{
Info,
Alarme,
Operation
}
}
Les noms de classe et propriétés doivent correspondre aux noms de table et de colonnes.
3. Classe d'accès aux données (DAL)
using System;
using System.Collections.Generic;
using System.Linq;
using ModelesMagasinAutomatique;
using SqlSugar;
namespace AccesDonneesMagasin
{
public class ServiceJournal
{
/// <summary>
/// Insère un enregistrement de journal
/// </summary>
public static bool AjouterJournal(JournalSysteme journal)
{
return HelperSqlSugar.ClientSqlSugar.Insertable(journal).ExecuteCommand() == 1;
}
/// <summary>
/// Recherche des journaux selon des critères
/// </summary>
public static List<JournalSysteme> ObtenirJournauxParCondition(
string debut, string fin, string typeJournal, string etatAlarme)
{
var requete = HelperSqlSugar.ClientSqlSugar.Queryable<JournalSysteme>()
.Where(j => SqlFunc.Between(j.Horodatage, debut, fin));
if (!string.IsNullOrEmpty(typeJournal))
{
var typeEnum = (TypeJournal)Enum.Parse(typeof(TypeJournal), typeJournal);
requete = requete.Where(j => j.Type == typeEnum);
}
if (!string.IsNullOrEmpty(etatAlarme))
{
requete = requete.Where(j => j.EtatAlarme == etatAlarme);
}
return requete.ToList();
}
}
}
4. Utilisation
4.1 Enregistrement de connexion
// Exemple dans l'événement de connexion réussie
ServiceJournal.AjouterJournal(new JournalSysteme("Connexion réussie", "Déclenché", TypeJournal.Operation, nomUtilisateur));
4.2 Enregistrement de déconnexion système
private void FormPrincipale_FormClosing(object sender, FormClosingEventArgs e)
{
// Écrire en base
if (!ServiceJournal.AjouterJournal(new JournalSysteme("Fermeture système", "Déclenché", TypeJournal.Operation, Program.AdminGlobal.NomUtilisateur)))
{
NLogHelper.Error("Erreur d'écriture du journal de déconnexion en base");
}
cts.Cancel();
}
4.3 Enregistrement de modification de paramètres
/// <summary>
/// Modifie un paramètre (réutilisable pour différents types d'objets)
/// </summary>
private void ModifierParametre<T>(string typeParametre, T objet)
{
var sb = new System.Text.StringBuilder();
foreach (var controle in this.tabControle.SelectedTab.Controls)
{
if (controle is NumericUpDown numeric)
{
string nomPropriete = numeric.Name;
string valeurActuelle = ObtenirValeurPropriete(objet, nomPropriete);
if (!string.IsNullOrEmpty(valeurActuelle) && numeric.Value.ToString() != valeurActuelle)
{
sb.Append(numeric.Tag.ToString() + " modifié à : " + numeric.Value.ToString() + "; ");
DefinirValeurPropriete(objet, nomPropriete, numeric.Value.ToString());
}
}
else if (controle is ComboBox cmb)
{
string nomPropriete = cmb.Name;
string valeurActuelle = ObtenirValeurPropriete(objet, nomPropriete);
if (!string.IsNullOrEmpty(valeurActuelle) && cmb.Text != valeurActuelle)
{
sb.Append(cmb.Tag.ToString() + " modifié à : " + cmb.Text + "; ");
DefinirValeurPropriete(objet, nomPropriete, cmb.Text);
}
}
}
if (sb.Length > 0)
{
var resultat = typeParametre == "Paramètres de base"
? moteurEx.SauvegarderParametresBase()
: moteurEx.SauvegarderParametresAvances();
if (resultat.Reussi)
{
if (!ServiceJournal.AjouterJournal(new JournalSysteme(sb.ToString(), "Déclenché", TypeJournal.Operation, Program.AdminGlobal.NomUtilisateur)))
{
NLogHelper.Error("Erreur d'écriture de la modification de paramètre en base");
}
MessageBox.Show(typeParametre + " modifié avec succès", "Modification paramètre");
}
else
{
MessageBox.Show(typeParametre + " échec de modification", "Modification paramètre");
}
}
else
{
MessageBox.Show("Aucune modification détectée", "Modification paramètre");
}
}
4.4 Informations de journal et d’alarme
/// <summary>
/// Ajoute un message de journal
/// </summary>
public void AjouterLog(int niveau, string message)
{
if (!this.listeInfos.InvokeRequired)
{
var item = new ListViewItem(HeureCourante, niveau);
item.SubItems.Add(message);
this.listeInfos.Items.Insert(0, item);
if (this.listeInfos.Items.Count > 100)
this.listeInfos.Items.RemoveAt(100);
}
else
{
this.listeInfos.Invoke(new Action(() =>
{
var item = new ListViewItem(HeureCourante, niveau);
item.SubItems.Add(message);
this.listeInfos.Items.Insert(0, item);
if (this.listeInfos.Items.Count > 100)
this.listeInfos.Items.RemoveAt(100);
}));
}
// Écrire en base
if (!ServiceJournal.AjouterJournal(new JournalSysteme(message, "Déclenché", TypeJournal.Info, Program.AdminGlobal.NomUtilisateur)))
{
NLogHelper.Error("Erreur d'écriture du journal info en base");
}
}
/// <summary>
/// Ajoute une alarme (déclenchement/acquittement)
/// </summary>
public void AjouterAlarme(string info, bool estDeclenchement)
{
if (estDeclenchement)
{
if (!ListeAlarmes.Contains(info))
ListeAlarmes.Add(info);
}
else
{
if (ListeAlarmes.Contains(info))
ListeAlarmes.Remove(info);
}
RafraichirAlarme();
// Écrire en base
if (!ServiceJournal.AjouterJournal(new JournalSysteme(info, estDeclenchement ? "Déclenché" : "Acquitté", TypeJournal.Alarme, Program.AdminGlobal.NomUtilisateur)))
{
NLogHelper.Error("Erreur d'écriture de l'alarme en base");
}
}
5. Écran de requête des journaxu
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using ModelesMagasinAutomatique;
using AccesDonneesMagasin;
namespace ApplicationMagasin
{
public partial class FormulaireJournal : Form
{
public FormulaireJournal()
{
InitializeComponent();
this.gridDonnees.AutoGenerateColumns = false;
this.gridDonnees.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
this.gridDonnees.Columns[0].Width = 150;
this.gridDonnees.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
this.gridDonnees.Columns[2].Width = 100;
this.gridDonnees.Columns[3].Width = 100;
this.gridDonnees.Columns[4].Width = 100;
this.chkInfo.Checked = true;
this.rdbDeclenche.Checked = true;
this.dateDebut.Value = this.dateFin.Value.AddHours(-3.0);
}
private void BtnRechercher_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
this.Invoke(new Action(() =>
{
ExecuterRecherche(
DateTime.Now.ToString(this.dateDebut.Text),
DateTime.Now.ToString(this.dateFin.Text),
ObtenirTypesJournal(),
ObtenirEtatAlarme()
);
}));
});
}
private void BtnRechercherAujourdhui_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
this.Invoke(new Action(() =>
{
ExecuterRecherche(
DateTime.Now.ToString("yyyy-MM-dd 00:00:00"),
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
ObtenirTypesJournal(),
ObtenirEtatAlarme()
);
}));
});
}
private List<string> ObtenirTypesJournal()
{
var types = new List<string>();
if (chkInfo.Checked) types.Add(chkInfo.Text);
if (chkAlarme.Checked) types.Add(chkAlarme.Text);
if (chkOperation.Checked) types.Add(chkOperation.Text);
return types;
}
private string ObtenirEtatAlarme()
{
if (rdbDeclenche.Checked) return "Déclenché";
if (rdbAcquite.Checked) return "Acquitté";
return string.Empty;
}
private void ExecuterRecherche(string debut, string fin, List<string> types, string etat)
{
DateTime t1 = Convert.ToDateTime(debut);
DateTime t2 = Convert.ToDateTime(fin);
if (t1 > t2)
{
MessageBox.Show("La date de début ne peut pas être postérieure à la date de fin.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (etat == "Tous") etat = string.Empty;
var resultats = new List<JournalSysteme>();
foreach (var type in types)
{
resultats.AddRange(ServiceJournal.ObtenirJournauxParCondition(debut, fin, type, etat));
}
// Trier par horodatage (important car les résultats sont groupés par type)
resultats.Sort((j1, j2) =>
{
var dt1 = Convert.ToDateTime(j1.Horodatage);
var dt2 = Convert.ToDateTime(j2.Horodatage);
return DateTime.Compare(dt1, dt2);
});
this.gridDonnees.DataSource = null;
this.gridDonnees.DataSource = resultats;
}
private void BtnExporter_Click(object sender, EventArgs e)
{
string chemin = HelperExcel.ExporterVersExcel(this.gridDonnees);
if (chemin != null)
MessageBox.Show("Export réussi.", "Export");
else
MessageBox.Show("Échec de l'export.", "Export");
}
}
}
6. Résultat visuel
La fenêtre de requête s'affiche avec les résultats triés par date, les filtres par type et état d'alarme, et un bouton d'export vers Excel.