C# : Comment gérer les dates nullables

C# : Comment gérer les dates nullables

CalendrierAujourd’hui, j’avais envie de rédiger un article sur un problème qui est survenu durant ma journée et qui m’a bien embêté. Avis donc aux âmes égarées, cet article sera technique et forcément peu abordable au commun des mortels qui n’aura jamais touché à une ligne de code de sa vie. Je me rattraperai une autre fois, en attendant, je vous invite à lire d’autres billets moins techniques.

C’est donc dans ma carrière de développeur que j’en suis arrivé à croiser à plusieurs reprises le même problème récurrent: la gestion des dates. A croire que personne n’a jamais pris le temps de réunir les principaux acteurs et à discuter sérieusement d’un standard universel. Bref, je m’égare.

Un problème connu depuis (trop) longtemps

En informatique, la gestion des dates a toujours été problématique sur deux aspects bien distincts: le format et les bornes.

Le format, parce qu’une date – prenons l’exemple du jour, le 18 septembre 2015 – peut être exprimée différemment selon le pays. Ainsi, 18/09/2015, 09/18/2015, 2015-18-09, 2015-09-18 et j’en passe et des meilleurs sont tous valables. Je dirais que le mieux géré à ce jour est le dernier cité, car c’est le plus logique pour un système informatique (notamment pour l’archivage de données, mais j’évite de m’étendre dans ces considérations).

Les bornes, parce que chaque système ne gère pas forcément les mêmes dates minimales et maximales. C’est ce qui explique l’origine même du « bug de l’an 2000 », provoqué par des confrères peu scrupuleux qui ont jugé utile de coder l’année sur deux digits (exemple: 97 pour 1997) en se disant probablement que tout le monde serait mort en 2000, de toute façon. On remarquera aussi que certains systèmes acceptent, comme date minimale, le 1er janvier 1970, d’autres le 1er janvier 1753 et d’autres encore le 1er janvier 1900. Vous voyez le foutoir que ça donne ? Bien.

Le problème du jour, en C#

En prologue, je souhaite encore préciser que mon expérience en C# s’avère être la plus agréable au niveau de la manipulation de dates, ça a vraiment été pensé intelligemment. Au contraire de mon expérience précédente avec Java, qui doit être renforcée à grand coup de librairies (exemple: JodaTime) pour éviter des burn-outs.

Mon problème d’aujourd’hui a concerné la persistance de dates potentiellement nulles en base de données. J’utilise le .Net Framework 4.5.1 ainsi qu’Entity Framework 6 couplé à une base MSSQL pour m’assister dans cette tâche. Comme je programme la plupart de mon temps des applications ASP.NET MVC, j’ai pris la bonne habitude de séparer mon code en couches applicatives, mais aussi de suivre le principe de modèle « métier » et modèle « présentation ». La transition entre les deux types de modèles se fait à l’aide de la librarie AutoMapper, que j’aime beaucoup et qui fait ça très bien.

Voici un exemple représentant mon code métier:

namespace PowerJPMApp.Business
{
    public class Employee
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
        public DateTime HireDate { get; set; }
        public DateTime? EndOfContractDate { get; set; }
        
        public Employee() { }
    }
}

Et voilà un exemple représentant mon code de présentation:

namespace PowerJPMApp.MVC.Models
{
    public class EmployeeViewModel
    {
        [Display(Name = "FirstName", ResourceType = typeof(Resources.I18nBundle))]
        public string FirstName { get; set; }
        
        [Display(Name = "LastName", ResourceType = typeof(Resources.I18nBundle))]
        public string LastName { get; set; }
        
        [Display(Name = "BirthDate", ResourceType = typeof(Resources.I18nBundle))]
        public DateTime BirthDate { get; set; }
        
        [Display(Name = "StartOfContract", ResourceType = typeof(Resources.I18nBundle))]
        public DateTime HireDate { get; set; }
        
        [Display(Name = "EndOfContract", ResourceType = typeof(Resources.I18nBundle))]
        public DateTime EndOfContractDate { get; set; }
        
        public Employee() { }
    }
}

Rien de bien horrible à voir. Notons cependant que la classe métier n’est pas polluée par toutes les annotations d’ASP.NET MVC dans cet exemple, et c’est une propreté qui me tient à coeur pendant mes développements. Raison pour laquelle j’ai opté pour ce principe de séparation.

Notez également que la propriété EndOfContractDate de la classe métier est nullable, tandis que celle de la classe de présentation ne l’est pas. C’est une limitation à laquelle je me suis heurté: le moteur de présentation Razor ne peut pas afficher une date nulle. Bon, ça ne me semble pas être une grande contrainte, je fais avec.

L’erreur obtenue

Tout semble indiquer que le code précédent va fonctionner. Et pourtant, lors de l’exécution et l’insertion d’une date « nulle », j’ai été surpris par l’exception suivante, provenant d’Entity Framework, remontant l’erreur depuis la base de données Microsoft SQL:

System.Data.SqlClient.SqlException : The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value.

Oups! En cherchant un peu sur Internet, je découvre avec stupeur le procédé derrière cette exception toute bête. En fait:

  1. Entity Framework détecte une date nulle que j’ai saisie (par défaut, MVC injecte « 01/01/0001 » à la place de null, ce qui n’est pas malin !)
  2. Entity Framework convertit l’objet DateTime qu’il a obtenu, en DateTime2, qui gère visiblement mieux le « null » que son copain.
  3. La date faussement nulle est envoyée à la base MSSQL
  4. La base de données râle, parce que « 01/01/0001 » est avant sa date minimale acceptée, soit « 01/01/1753 » (ah, bravo !)

Alors, comment résoudre le problème ? Deux petites minutes d’intense réflexion (le temps de tirer un café avec une machine assez lente mais appréciée tout de même pour sa disponibilité à toute épreuve) et une solution m’est apparue.

La solution miracle

La solution est toute bête, en somme. Lorsqu’on récupère la date du 01/01/0001 dans une variable DateTime et qu’on doit la transférer à une variable DateTime nullable, il suffit de lui appliquer un traitement supplémentaire, à savoir:

EmployeeViewModel viewModel = ...; // on l'obtient d'ASP.NET MVC
Employee model = AutoMapper.Mapper.Map<Employee>(viewModel);

if (model.EndOfContractDate == DateTime.MinValue) {
    model.EndOfContractDate = null;
}
// On sauve le modèle métier

Et voilà ! Une DateTime nulle, transmise à Entity Framework, qui ne va plus s’embarrasser de la conversion à DateTime2 et va simplement envoyer « null » à la base de données. C’était bête, pas vrai ?

Bien. Sur ce, je vais aller reposer ma petite tête pour me préparer mentalement à de nouvelles aventures palpitantes dans ma vie de développeur !

P.-S: pour résoudre mon problème, ce sujet sur StackOverflow m’a bien aidé, d’ailleurs.

Source de l’image: Pixabay.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.