Entity Framework 6 Code first Valeur par défaut


204

Existe-t-il un moyen "élégant" de donner à une propriété spécifique une valeur par défaut?

Peut-être par DataAnnotations, quelque chose comme:

[DefaultValue("true")]
public bool Active { get; set; }

Je vous remercie.


Peut-être essayer dans le constructeur this.Active = true;? Je pense que la valeur DB aura la priorité lors de la récupération, mais attention si nouveau, puis attacher une entité pour une mise à jour sans récupération d'abord, car le suivi des modifications peut voir cela comme si vous vouliez mettre à jour la valeur. Commentaire parce que je n'ai pas utilisé EF depuis longtemps, et j'ai l'impression que c'est une photo dans le noir.
AaronLS

3
Merci pour la réponse, j'ai utilisé cette méthode jusqu'ici stackoverflow.com/a/5032578/2913441 mais je pensais qu'il y avait peut-être une meilleure façon.
marino-krk

2
public bool Inactive { get; set; }😉
Jesse Hufstetler

comme le disent les documents Microsoft "Vous ne pouvez pas définir une valeur par défaut à l'aide des annotations de données."
hmfarimani

Réponses:


167

Vous pouvez le faire en modifiant manuellement la première migration du code:

public override void Up()
{    
   AddColumn("dbo.Events", "Active", c => c.Boolean(nullable: false, defaultValue: true));
} 

6
Je ne suis pas sûr que cela ne fonctionnera que si OP ne spécialement mis Activeà truelors de la création d' un Eventobjet aussi bien. La valeur par défaut est toujours falsesur une propriété bool non nullable, donc à moins qu'elle ne soit modifiée, c'est ce que le framework d'entité enregistrera dans la base de données. Ou est-ce que je manque quelque chose?
GFoley83

6
@ GFoley83, oui, vous avez raison. Cette méthode n'ajoute que la contrainte par défaut au niveau de la base de données. Pour une solution complète, vous devez également attribuer une valeur par défaut au constructeur de l'entité ou utiliser une propriété avec un champ soutenu comme indiqué dans la réponse ci
gdbdable

1
Cela fonctionne pour les types de base. Pour quelque chose comme DATETIMEOFFSET, utilisez le, defaultValueSql: "SYSDATETIMEOFFSET", et NON le defaultValue car ce defaultValue: System.DateTimeOffset.Now, se résoudra en une chaîne de la valeur actuelle du système datetimeoffset du système.
OzBob

3
AFAIK vos modifications manuelles seront perdues en cas de ré-échafaudage de la migration
Alexander Powolozki

1
@ninbit je pense que vous devez shuld écrire la migration pour la supprimer d'abord au niveau de la base de données, puis changer votre mappage DAL
gdbdable

74

Cela fait un moment, mais laisser une note aux autres. J'ai obtenu ce qui est nécessaire avec un attribut et j'ai décoré mes champs de classe de modèle avec cet attribut comme je le souhaite.

[SqlDefaultValue(DefaultValue = "getutcdate()")]
public DateTime CreatedDateUtc { get; set; }

Vous avez l'aide de ces 2 articles:

Ce que j'ai fait:

Définir un attribut

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class SqlDefaultValueAttribute : Attribute
{
    public string DefaultValue { get; set; }
}

Dans le "OnModelCreating" du contexte

modelBuilder.Conventions.Add( new AttributeToColumnAnnotationConvention<SqlDefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue));

Dans le SqlGenerator personnalisé

private void SetAnnotatedColumn(ColumnModel col)
{
    AnnotationValues values;
    if (col.Annotations.TryGetValue("SqlDefaultValue", out values))
    {
         col.DefaultValueSql = (string)values.NewValue;
    }
}

Ensuite, dans le constructeur de configuration de migration, enregistrez le générateur SQL personnalisé.

SetSqlGenerator("System.Data.SqlClient", new CustomMigrationSqlGenerator());

6
Vous pouvez même le faire globalement sans mettre [SqlDefaultValue(DefaultValue = "getutcdate()")]sur chaque entité. 1) Retirez simplement modelBuilder.Conventions.Add( new AttributeToColumnAnnotationConvention<SqlDefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue)); 2) AjoutezmodelBuilder.Properties().Where(x => x.PropertyType == typeof(DateTime)).Configure(c => c.HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed).HasColumnAnnotation("SqlDefaultValue", "getdate()"));
Daniel Skowroński

1
Où est le SqlGenerator personnalisé s'il vous plaît?
ᴍᴀᴛᴛ ʙᴀᴋᴇʀ

3
SqlGenerator personnalisé est venu d'ici: andy.mehalick.com/2014/02/06/…
ravinsp

1
@ravinsp pourquoi ne pas personnaliser la classe MigrationCodeGenerator pour avoir la migration avec les bonnes informations plutôt que le code SqlGenerator? Le code SQL est la dernière étape ...
Alex

@Alex vaut la peine d'essayer! Cela fonctionnerait également et serait plus élégant que d'injecter du code SQL. Mais je ne suis pas sûr de la complexité de la substitution du C # MigrationCodeGenerator.
ravinsp

73

Les réponses ci-dessus ont vraiment aidé, mais n'ont fourni qu'une partie de la solution. Le problème majeur est que dès que vous supprimez l'attribut Valeur par défaut, la contrainte sur la colonne dans la base de données ne sera pas supprimée. La valeur par défaut précédente restera donc toujours dans la base de données.

Voici une solution complète au problème, y compris la suppression des contraintes SQL sur la suppression des attributs. Je réutilise également le natif de .NET FrameworkDefaultValue attribut .

Usage

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[DefaultValue("getutcdate()")]
public DateTime CreatedOn { get; set; }

Pour que cela fonctionne, vous devez mettre à jour IdentityModels.cs et Configuration.cs fichiers

Fichier IdentityModels.cs

Ajoutez / mettez à jour cette méthode dans votre ApplicationDbContextclasse

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
            base.OnModelCreating(modelBuilder);
            var convention = new AttributeToColumnAnnotationConvention<DefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.SingleOrDefault().Value.ToString());
            modelBuilder.Conventions.Add(convention);
}

Fichier Configuration.cs

Mettez à jour votre Configurationconstructeur de classe en enregistrant un générateur SQL personnalisé, comme ceci:

internal sealed class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
{
    public Configuration()
    {
        // DefaultValue Sql Generator
        SetSqlGenerator("System.Data.SqlClient", new DefaultValueSqlServerMigrationSqlGenerator());
    }
}

Ensuite, ajoutez une classe de générateur SQL personnalisée (vous pouvez l'ajouter au fichier Configuration.cs ou à un fichier distinct)

internal class DefaultValueSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
    private int dropConstraintCount = 0;

    protected override void Generate(AddColumnOperation addColumnOperation)
    {
        SetAnnotatedColumn(addColumnOperation.Column, addColumnOperation.Table);
        base.Generate(addColumnOperation);
    }

    protected override void Generate(AlterColumnOperation alterColumnOperation)
    {
        SetAnnotatedColumn(alterColumnOperation.Column, alterColumnOperation.Table);
        base.Generate(alterColumnOperation);
    }

    protected override void Generate(CreateTableOperation createTableOperation)
    {
        SetAnnotatedColumns(createTableOperation.Columns, createTableOperation.Name);
        base.Generate(createTableOperation);
    }

    protected override void Generate(AlterTableOperation alterTableOperation)
    {
        SetAnnotatedColumns(alterTableOperation.Columns, alterTableOperation.Name);
        base.Generate(alterTableOperation);
    }

    private void SetAnnotatedColumn(ColumnModel column, string tableName)
    {
        AnnotationValues values;
        if (column.Annotations.TryGetValue("SqlDefaultValue", out values))
        {
            if (values.NewValue == null)
            {
                column.DefaultValueSql = null;
                using (var writer = Writer())
                {
                    // Drop Constraint
                    writer.WriteLine(GetSqlDropConstraintQuery(tableName, column.Name));
                    Statement(writer);
                }
            }
            else
            {
                column.DefaultValueSql = (string)values.NewValue;
            }
        }
    }

    private void SetAnnotatedColumns(IEnumerable<ColumnModel> columns, string tableName)
    {
        foreach (var column in columns)
        {
            SetAnnotatedColumn(column, tableName);
        }
    }

    private string GetSqlDropConstraintQuery(string tableName, string columnName)
    {
        var tableNameSplittedByDot = tableName.Split('.');
        var tableSchema = tableNameSplittedByDot[0];
        var tablePureName = tableNameSplittedByDot[1];

        var str = $@"DECLARE @var{dropConstraintCount} nvarchar(128)
SELECT @var{dropConstraintCount} = name
FROM sys.default_constraints
WHERE parent_object_id = object_id(N'{tableSchema}.[{tablePureName}]')
AND col_name(parent_object_id, parent_column_id) = '{columnName}';
IF @var{dropConstraintCount} IS NOT NULL
    EXECUTE('ALTER TABLE {tableSchema}.[{tablePureName}] DROP CONSTRAINT [' + @var{dropConstraintCount} + ']')";

        dropConstraintCount = dropConstraintCount + 1;
        return str;
    }
}

2
Cette approche a parfaitement fonctionné pour moi. Une amélioration que j'ai apportée a été de remplacer également Generate (CreateTableOperation createTableOperation) et Generate (AddColumnOperation addColumnOperation) avec la même logique afin que ces scénarios soient également interceptés. Je vérifie également uniquement les valeurs.NewValue est null car je voulais que ma valeur par défaut soit une chaîne vide.
Delorian

2
@Delorian J'ai mis à jour ma réponse, merci pour vos commentaires
Jevgenij Martynenko

1
J'ai apporté une modification à votre publication pour prendre en charge les cas où une restauration supprime plus d'une contrainte. Votre script générerait une erreur indiquant que @con a déjà été déclaré. J'ai créé une variable privée pour contenir un compteur et l'incrémenter simplement. J'ai également changé le format de la contrainte de suppression pour mieux correspondre à ce que EF envoie à SQL lors de la création de la contrainte. Excellent travail là-dessus!
Brad

1
Merci pour la solution mais j'ai deux problèmes: 1. Les noms de table ont besoin de crochets. 2. Dans la mise à jour, la nouvelle valeur n'est pas définie et la valeur par défaut est définie à la place!
Omid-RH

1
Dois-je définir l' [DatabaseGenerated(DatabaseGeneratedOption.Computed)]attribut? Si oui, pourquoi? Dans mes tests, cela ne semblait avoir aucun effet en le laissant de côté.
RamNow

26

Vos propriétés de modèle ne doivent pas nécessairement être des «propriétés automatiques» Même si c'est plus facile. Et l'attribut DefaultValue n'est vraiment que des métadonnées informatives. La réponse acceptée ici est une alternative à l'approche constructeur.

public class Track
{

    private const int DEFAULT_LENGTH = 400;
    private int _length = DEFAULT_LENGTH;
    [DefaultValue(DEFAULT_LENGTH)]
    public int LengthInMeters {
        get { return _length; }
        set { _length = value; }
    }
}

contre.

public class Track
{
    public Track()
    {
        LengthInMeters = 400;   
    }

    public int LengthInMeters { get; set; }        
}

Cela ne fonctionnera que pour les applications créant et consommant des données à l'aide de cette classe spécifique. Habituellement, ce n'est pas un problème si le code d'accès aux données est centralisé. Pour mettre à jour la valeur dans toutes les applications, vous devez configurer la source de données pour définir une valeur par défaut. La réponse de Devi montre comment cela peut être fait en utilisant des migrations, SQL ou la langue parlée par votre source de données.


21
Remarque: cela ne définira pas de valeur par défaut dans la base de données . Les autres programmes n'utilisant pas votre entité n'obtiendront pas cette valeur par défaut.
Eric J.18

Cette partie de la réponse, mais cela ne fonctionne pas si vous insérez des enregistrements autrement que via Entity Framework. De plus, si vous créez une nouvelle colonne non nulle sur une table, cela ne vous donnera pas la possibilité de définir la valeur par défaut pour les enregistrements existants. @devi a un ajout précieux ci-dessous.
d512

Pourquoi votre première approche est-elle "correcte"? Allez-vous rencontrer des problèmes imprévus avec l'approche constructeur?
WiteCastle

une question d'opinion, et ces changements :) Et probablement pas.
calebboyd

2
Dans certaines circonstances, j'ai eu des problèmes avec l'approche constructeur; la définition d'une valeur par défaut dans le champ de support semble être la solution la moins invasive.
Lucent Fox

11

Ce que j'ai fait, j'ai initialisé des valeurs dans le constructeur de l'entité

Remarque: les attributs DefaultValue ne définissent pas automatiquement les valeurs de vos propriétés, vous devez le faire vous-même


4
Le problème avec le constructeur définissant la valeur est qu'EF effectuera une mise à jour de la base de données lors de la validation de la transaction.
Luka

Le modèle crée d'abord les valeurs par défaut de cette façon
Lucas

La valeur par défaut est une espèce qui vit dans des pays lointains et étrangers, trop timide pour rencontrer vos propriétés par elle-même. Ne faites pas de bruit, il est facilement effrayé - s'il se rapproche suffisamment pour l'entendre. +1 pour avoir déclaré que ce n'était pas du tout évident.
Risadinha

8

Après le commentaire de @SedatKapanoglu, j'ajoute toute mon approche qui fonctionne, car il avait raison, le simple fait d'utiliser l'API couramment ne fonctionne pas.

1- Créer un générateur de code personnalisé et remplacer Générer pour un ColumnModel.

   public class ExtendedMigrationCodeGenerator : CSharpMigrationCodeGenerator
{

    protected override void Generate(ColumnModel column, IndentedTextWriter writer, bool emitName = false)
    {

        if (column.Annotations.Keys.Contains("Default"))
        {
            var value = Convert.ChangeType(column.Annotations["Default"].NewValue, column.ClrDefaultValue.GetType());
            column.DefaultValue = value;
        }


        base.Generate(column, writer, emitName);
    }

}

2- Attribuez le nouveau générateur de code:

public sealed class Configuration : DbMigrationsConfiguration<Data.Context.EfSqlDbContext>
{
    public Configuration()
    {
        CodeGenerator = new ExtendedMigrationCodeGenerator();
        AutomaticMigrationsEnabled = false;
    }
}

3- Utilisez une API fluide pour créer l'annotation:

public static void Configure(DbModelBuilder builder){    
builder.Entity<Company>().Property(c => c.Status).HasColumnAnnotation("Default", 0);            
}

Veuillez voir ma solution complète, j'ai ajouté toute ma mise en œuvre de la façon dont cela fonctionne, merci.
Denny Puig

5

C'est simple! Annotez simplement avec requis.

[Required]
public bool MyField { get; set; }

la migration qui en résultera sera:

migrationBuilder.AddColumn<bool>(
name: "MyField",
table: "MyTable",
nullable: false,
defaultValue: false);

Si vous voulez true, remplacez defaultValue par true dans la migration avant de mettre à jour la base de données


la migration générée automatiquement peut alors être modifiée et vous oubliez la valeur par défaut
Fernando Torres

Simple et fonctionne, aide à ajouter rapidement une colonne à une table existante avec une contrainte de clé étrangère sur la nouvelle colonne. Merci
po10cySA

Et si nous avons besoin que la valeur par défaut soit true?
Christos Lytras

5

J'avoue que mon approche échappe à tout le concept de "Code First". Mais si vous avez la possibilité de simplement changer la valeur par défaut dans le tableau lui-même ... c'est beaucoup plus simple que les longueurs que vous devez parcourir ci-dessus ... Je suis juste trop paresseux pour faire tout ce travail!

Il semble presque que l'idée originale des affiches fonctionnerait:

[DefaultValue(true)]
public bool IsAdmin { get; set; }

Je pensais qu'ils avaient juste fait l'erreur d'ajouter des citations ... mais hélas pas une telle intuition. Les autres suggestions étaient tout simplement trop pour moi (étant donné que j'ai les privilèges nécessaires pour entrer dans le tableau et apporter les modifications ... où tous les développeurs ne le seront pas dans toutes les situations). En fin de compte, je l'ai fait à l'ancienne. J'ai défini la valeur par défaut dans la table SQL Server ... Je veux dire vraiment, déjà assez! REMARQUE: J'ai en outre testé la création d'une base de données d'ajout-migration et de mise à jour et les modifications sont bloquées. entrez la description de l'image ici


1

Il suffit de surcharger le constructeur par défaut de la classe Model et de passer tout paramètre pertinent que vous pouvez utiliser ou non. Par cela, vous pouvez facilement fournir des valeurs par défaut pour les attributs. Voici un exemple.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Aim.Data.Domain
{
    [MetadataType(typeof(LoginModel))]
    public partial class Login
    {       
        public Login(bool status)
        {
            this.CreatedDate = DateTime.Now;
            this.ModifiedDate = DateTime.Now;
            this.Culture = "EN-US";
            this.IsDefaultPassword = status;
            this.IsActive = status;
            this.LoginLogs = new HashSet<LoginLog>();
            this.LoginLogHistories = new HashSet<LoginLogHistory>();
        }


    }

    public class LoginModel
    {

        [Key]
        [ScaffoldColumn(false)] 
        public int Id { get; set; }
        [Required]
        public string LoginCode { get; set; }
        [Required]
        public string Password { get; set; }
        public string LastPassword { get; set; }     
        public int UserGroupId { get; set; }
        public int FalseAttempt { get; set; }
        public bool IsLocked { get; set; }
        public int CreatedBy { get; set; }       
        public System.DateTime CreatedDate { get; set; }
        public Nullable<int> ModifiedBy { get; set; }      
        public Nullable<System.DateTime> ModifiedDate { get; set; }       
        public string Culture { get; set; }        
        public virtual ICollection<LoginLog> LoginLogs { get; set; }
        public virtual ICollection<LoginLogHistory> LoginLogHistories { get; set; }
    }

}

Cette suggestion est totalement logique côté client. Cela "fonctionne" tant que vous n'interagirez qu'avec la base de données à l'aide de l'application. Dès que quelqu'un souhaite insérer un enregistrement manuellement ou à partir d'une autre application, vous réalisez l'inconvénient qu'il n'y a pas d'expression par défaut dans le schéma et que cela ne peut pas être étendu à d'autres clients. Bien qu'il n'ait pas été explicitement indiqué, ce sujet légitime implique la nécessité d'obtenir EF pour créer une migration qui place une expression par défaut dans la définition de colonne.
Tom

0

Considérons que vous avez un nom de classe nommé Products et que vous avez un champ IsActive. vous avez juste besoin d'un constructeur create:

Public class Products
{
    public Products()
    {
       IsActive = true;
    }
 public string Field1 { get; set; }
 public string Field2 { get; set; }
 public bool IsActive { get; set; }
}

Votre valeur par défaut IsActive est alors True!

Edite :

si vous voulez le faire avec SQL, utilisez cette commande:

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

Ce n'était tout simplement pas la question. Il s'agissait de contraintes de valeur par défaut dans la base de données elle-même.
Gábor

4
@Hatef, votre modification s'applique uniquement à EF Core. La question concerne EF 6.
Michael

3
Ce HasDefaultValueSql n'est pas disponible dans EF6
tatigo

0

Dans EF core publié le 27 juin 2016, vous pouvez utiliser l'API couramment pour définir la valeur par défaut. Accédez à la classe ApplicationDbContext, recherchez / créez le nom de la méthode OnModelCreating et ajoutez l'API courante suivante.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<YourTableName>()
        .Property(b => b.Active)
        .HasDefaultValue(true);
}

0

Dans .NET Core 3.1, vous pouvez effectuer les opérations suivantes dans la classe de modèle:

    public bool? Active { get; set; } 

Dans le DbContext OnModelCreating, vous ajoutez la valeur par défaut.

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Foundation>()
            .Property(b => b.Active)
            .HasDefaultValueSql("1");

        base.OnModelCreating(modelBuilder);
    }

Entraînant ce qui suit dans la base de données

entrez la description de l'image ici

Remarque: Si vous n'avez pas de valeur nulle (bool?) Pour votre propriété, vous obtiendrez l'avertissement suivant

The 'bool' property 'Active' on entity type 'Foundation' is configured with a database-generated default. This default will always be used for inserts when the property has the value 'false', since this is the CLR default for the 'bool' type. Consider using the nullable 'bool?' type instead so that the default will only be used for inserts when the property value is 'null'.

-3

J'ai trouvé qu'il suffit d'utiliser Auto-Property Initializer sur la propriété d'entité pour faire le travail.

Par exemple:

public class Thing {
    public bool IsBigThing{ get; set; } = false;
}

2
On pourrait penser que cela fonctionnerait, avec le code en premier. Mais ce n'était pas le cas avec EF 6.2.
user1040323

-4

Hmm ... Je fais d'abord DB, et dans ce cas, c'est en fait beaucoup plus facile. EF6 non? Ouvrez simplement votre modèle, faites un clic droit sur la colonne pour laquelle vous souhaitez définir une valeur par défaut, choisissez les propriétés et vous verrez un champ "DefaultValue". Remplissez-le et enregistrez. Il configurera le code pour vous.

Votre kilométrage peut d'abord varier sur le code, je n'ai pas travaillé avec cela.

Le problème avec beaucoup d'autres solutions, c'est que même si elles peuvent fonctionner initialement, dès que vous reconstruisez le modèle, il va jeter tout code personnalisé que vous avez inséré dans le fichier généré par la machine.

Cette méthode fonctionne en ajoutant une propriété supplémentaire au fichier edmx:

<EntityType Name="Thingy">
  <Property Name="Iteration" Type="Int32" Nullable="false" **DefaultValue="1"** />

Et en ajoutant le code nécessaire au constructeur:

public Thingy()
{
  this.Iteration = 1;

-5

Définissez la valeur par défaut de la colonne dans la table dans MSSQL Server et dans l'attribut add de code de classe, comme ceci:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

pour la même propriété.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.