Modéliser des objets complexes avec JSON
Introduction
Les applications modernes manipulent de plus en plus de données semi-structurées : préférences utilisateur, paramètres de configuration, métadonnées, informations provenant de services externes ou encore données issues de l'intelligence artificielle. Bien qu'une modélisation relationnelle classique reste adaptée à de nombreux cas d'usage, elle peut parfois devenir trop rigide ou conduire à une multiplication des tables et des jointures.
Avec Entity Framework Core 10, Microsoft améliore significativement la prise en charge des colonnes JSON et des types complexes. Il devient possible de stocker directement des sous-objets dans une colonne JSON, de les interroger avec LINQ et de les mettre à jour efficacement, sans charger les entités en mémoire. Les types complexes gagnent également en souplesse, notamment grâce au support des propriétés optionnelles et à leur intégration naturelle avec le stockage JSON.
Ces évolutions permettent de modéliser plus simplement des objets métier sans identité propre, tout en bénéficiant de la puissance du mapping objet-relationnel proposé par Entity Framework Core.
En conclusion : Avec Entity Framework Core 10, les types complexes mappés dans des colonnes JSON permettent de modéliser naturellement des sous-objets métier sans identité propre, tout en conservant la possibilité de les interroger et de les mettre à jour efficacement directement depuis LINQ.
Exemple
Prenons l'exemple de préférences des utilisateurs dans un logiciel. Avant EF Core 10, on aurait souvent créé plusieurs colonnes :
Ou bien une table supplémentaire, avec une clé étrangère, nommée CustomerId, reliée au champ Id de la table Customer :
Dans notre application .NET, on peut implémenter en C# les entités et propriétés correspondantes :
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = "";
public CustomerPreferences Preferences { get; set; } = new();
}
public class CustomerPreferences
{
public string Lang { get; set; } = "fr";
public string Theme { get; set; } = "light";
public bool IsNewsletterSubs { get; set; }
}
L'entité CustomerPreferences est un type complexe : elle ne possède pas d'identifiant et n'a pas vocation à être stocké dans une table dédiée.
Configuration
Voici la configuration à appliquer lors de la configuration du modèle :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>()
.ComplexProperty(
c => c.Preferences,
preferences =>
{
preferences.ToJson();
});
}
Cette configuration permet de stocker les préférences dans une seule colonne de la table Customer. Ainsi, dans cette table, nous avons les données suivantes :
Id Name Preferences
1 James {"Lang":"fr","Theme":"dark","IsNewsletterSubs":true,}
2 Laurence {"Lang":"en","Theme":"light","IsNewsletterSubs":false}
L'avantage de cette solution est qu'elle permet d'éviter :
-
D'ajouter une table supplémentaire avec des colonnes à gérer.
-
De réduire les jointures lors de l'exécution.
Requêtage
Si nous souhaitons obtenir la liste des utilisateurs ayant choisi la langue française dans leur configuration, nous pouvons écrire la requête LINQ suivante :
Mise à jour
Si nous souhaitons activer la newsletter pour tous les utilisateurs dont la langue préférée est le français :
await context.Customers
.Where(c => c.Preferences.Language == "fr")
.ExecuteUpdateAsync(setters =>
setters.SetProperty(
c => c.Preferences.IsNewsletterSubs,
true));
Les avantages de cette implémentation sont les suivants :
-
Pas d'encombrement de la mémoire : elle ne charge aucun objet en mémoire.
-
Les objets ne dont pas suivis dans le
DbContext. -
La requête SQL générée est un UPDATE SQL ciblant la propriété dans le flux JSON.