Valeurs générées

Les colonnes de base de données peuvent avoir leurs valeurs générées de différentes manières : les colonnes de clé primaire sont fréquemment des entiers incrémentant automatiquement, d’autres colonnes ont des valeurs par défaut ou calculées, etc. Cette page fournit des informations détaillés sur les différents modèles pour la génération de valeurs de configuration avec EF Core.

Valeurs par défaut

Sur les bases de données relationnelles, une colonne peut être configurée avec une valeur par défaut ; si une ligne est insérée sans valeur pour cette colonne, la valeur par défaut est utilisée.

Vous pouvez configurer une valeur par défaut sur une propriété :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Rating)
        .HasDefaultValue(3);
}

Vous pouvez également spécifier un fragment SQL utilisé pour calculer la valeur par défaut :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Created)
        .HasDefaultValueSql("getdate()");
}

Colonnes calculées

Sur la plupart des bases de données relationnelles, une colonne peut être configurée pour avoir sa valeur calculée dans la base de données, généralement avec une expression faisant référence à d’autres colonnes :

modelBuilder.Entity<Person>()
    .Property(p => p.DisplayName)
    .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");

La valeur ci-dessus crée une colonne calculée virtuelle, dont la valeur est calculée chaque fois qu’elle est extraite de la base de données. Vous pouvez également spécifier qu’une colonne calculée doit être stockée (parfois appelée persistante), ce qui signifie qu’elle est calculée sur chaque mise à jour de la ligne et qu’elle est stockée sur le disque en même temps que les colonnes régulières :

modelBuilder.Entity<Person>()
    .Property(p => p.NameLength)
    .HasComputedColumnSql("LEN([LastName]) + LEN([FirstName])", stored: true);

Clés primaires

Par convention, les clés primaires non composites de type court, int, long ou Guid sont configurées pour avoir des valeurs générées pour les entités insérées si une valeur n’est pas fournie par l’application. Votre fournisseur de base de données s’occupe généralement de la configuration nécessaire. Par exemple, une clé primaire numérique dans SQL Server est automatiquement configurée pour être une colonne IDENTITY.

Pour plus d’informations, consultez la documentation sur les clés et les conseils relatifs aux stratégies de mappage d’héritage spécifiques.

Configuration explicite de la génération de valeurs

Nous avons vu ci-dessus qu’EF Core configure automatiquement la génération de valeurs pour les clés primaires, mais nous pourrions vouloir faire de même pour les propriétés non clés. Vous pouvez configurer n’importe quelle propriété pour que sa valeur soit générée pour les entités insérées comme suit :

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public DateTime Inserted { get; set; }
}

De même, une propriété peut être configurée afin que sa valeur soit générée lors de l’ajout ou de la mise à jour :

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime LastUpdated { get; set; }
}

Contrairement aux valeurs par défaut ou aux colonnes calculées, nous ne spécifions pas la façon dont les valeurs doivent être générées ; cela dépend du fournisseur de base de données utilisé. Les fournisseurs de base de données peuvent configurer automatiquement la génération de valeurs pour certains types de propriétés, mais d’autres peuvent vous obliger à configurer manuellement la façon dont la valeur est générée.

Par exemple, sur SQL Server, lorsqu’une propriété GUID est configurée en tant que clé primaire, le fournisseur effectue automatiquement la génération de valeurs côté client, à l’aide d’un algorithme pour générer des valeurs GUID séquentielles optimales. Toutefois, la spécification d’une propriété DateTime ValueGeneratedOnAdd n’aura aucun effet (voir la section ci-dessous pour en savoir plus sur la génération de valeur DateTime).

De même, les propriétés byte[] configurées comme générées lors de l’ajout ou de la mise à jour et marquées comme jetons d’accès concurrentiel sont configurées avec le type de données rowversion, afin que les valeurs soient générées automatiquement dans la base de données. Toutefois, spécifier ValueGeneratedOnAdd n’a aucun effet.

Consultez la documentation de votre fournisseur pour connaître les techniques de génération de valeur spécifiques qu’il prend en charge. La documentation sur la génération de valeurs SQL Server est disponible ici.

Générer les valeurs de date et d’heure

Une requête courante consiste à avoir une colonne de base de données qui contient la date et l’heure spécifiant quand la ligne a été insérée pour la première fois (valeur générée lors de l’ajout) ou quand la dernière mise à jour a eu lieu (valeur générée lors de l’ajout ou de la mise à jour). Comme il existe différentes stratégies à suivre, les fournisseurs EF Core ne configurent généralement pas automatiquement la génération de valeurs pour les colonnes de date et d’heure. Vous devez le configurer vous-même.

Horodatage de création

La configuration d’une colonne de date et d’heure indiquant l’horodatage de création de la ligne consiste généralement à créer une valeur par défaut avec la fonction SQL appropriée. Par exemple, sur SQL Server, vous pouvez utiliser les éléments suivants :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Created)
        .HasDefaultValueSql("getdate()");
}

Veillez à sélectionner la fonction appropriée, car plusieurs peuvent exister (par exemple, GETDATE() par rapport à GETUTCDATE()).

Horodatage de mise à jour

Bien que les colonnes calculées stockées semblent être une bonne solution pour gérer les horodatages de mise à jour, les bases de données ne permettent généralement pas de spécifier des fonctions telles que GETDATE() dans une colonne calculée. Vous pouvez cependant configurer un déclencheur de base de données pour obtenir le même effet :

CREATE TRIGGER [dbo].[Blogs_UPDATE] ON [dbo].[Blogs]
    AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    IF ((SELECT TRIGGER_NESTLEVEL()) > 1) RETURN;

    UPDATE B
    SET LastUpdated = GETDATE()
    FROM dbo.Blogs AS B
    INNER JOIN INSERTED AS I
        ON B.BlogId = I.BlogId
END

Pour plus d’informations sur la création de déclencheurs, consultez la documentation sur l’utilisation de SQL brut dans les migrations.

Génération de valeur de substitution

Bien qu’une propriété soit configurée pour la génération de valeurs, dans de nombreux cas, vous pouvez toujours spécifier explicitement une valeur pour celle-ci. Le fait de savoir si cela fonctionne réellement dépend du mécanisme de génération de valeur spécifique qui a été configuré ; bien que vous puissiez spécifier une valeur explicite au lieu d’utiliser la valeur par défaut d’une colonne, la même chose ne peut pas être effectuée avec des colonnes calculées.

Pour remplacer la génération de valeurs par une valeur explicite, définissez simplement la propriété sur n’importe quelle valeur qui n’est pas la valeur par défaut CLR pour le type de cette propriété (null pour string, 0 pour int, Guid.Empty pour Guid, etc.).

Remarque

La tentative d’insertion de valeurs explicites dans SQL Server IDENTITY échoue par défaut ; consultez cette documentation pour obtenir une solution alternative.

Pour fournir une valeur explicite pour les propriétés qui ont été configurées en tant que valeur générée lors de l’ajout ou de la mise à jour, vous devez également configurer la propriété comme suit :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().Property(b => b.LastUpdated)
        .ValueGeneratedOnAddOrUpdate()
        .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);
}

Aucune génération de valeur

Outre des scénarios spécifiques tels que ceux décrits ci-dessus, les propriétés n’ont généralement aucune génération de valeur configurée. Cela signifie qu’il appartient à l’application de toujours fournir une valeur à enregistrer dans la base de données. Cette valeur doit être affectée à de nouvelles entités avant d’être ajoutées au contexte.

Toutefois, dans certains cas, vous pouvez désactiver la génération de valeurs qui a été configurée par convention. Par exemple, une clé primaire de type int est généralement configurée implicitement en tant que module complémentaire généré par la valeur (par exemple, colonne d’identité sur SQL Server). Vous pouvez désactiver cette génération comme suit :

public class Blog
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int BlogId { get; set; }

    public string Url { get; set; }
}