Entity Framework 6 Code first Valore predefinito


204

esiste un modo "elegante" per assegnare a una proprietà specifica un valore predefinito?

Forse da DataAnnotations, qualcosa del tipo:

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

Grazie.


Forse provare nel costruttore this.Active = true;? Penso che il valore del DB avrà la precedenza durante il recupero, ma attenzione se il nuovo viene quindi associato un'entità per un aggiornamento senza un primo recupero, poiché il rilevamento delle modifiche potrebbe vedere questo come si desidera aggiornare il valore. Commenta perché non uso EF da molto tempo e mi sembra che sia uno scatto al buio.
AaronLS

3
Grazie per la risposta, ho usato questo metodo finora stackoverflow.com/a/5032578/2913441 ma ho pensato che forse c'è un modo migliore.
marino-krk,

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

come dicono i documenti Microsoft "Non è possibile impostare un valore predefinito utilizzando Annotazioni dati".
hmfarimani,

Risposte:


167

Puoi farlo modificando manualmente la prima migrazione del codice:

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

6
Non sono sicuro che funzionerà se OP non è impostato appositamente Activeanche truedurante la creazione di un Eventoggetto. Il valore predefinito è sempre falsesu una proprietà bool non nullable, quindi a meno che non venga modificato, questo è ciò che l'entità framework salverà nel db. Oppure mi sfugge qualcosa?
GFoley83,

6
@ GFoley83, sì, hai ragione. Questo metodo aggiunge solo il vincolo predefinito a livello di database. Per una soluzione completa è inoltre necessario assegnare un valore predefinito nel costruttore dell'entità o utilizzare la proprietà con il campo
protetto

1
Funziona con tipi di base. Per qualcosa come DATETIMEOFFSET, utilizzare, defaultValueSql: "SYSDATETIMEOFFSET" e NON defaultValue come defaultValue: System.DateTimeOffset.Now, si risolverà in una stringa del valore di datatimeoffset corrente del sistema.
OzBob,

3
AFAIK le tue modifiche manuali andranno perse in caso di
reimpalcatura

1
@ninbit penso che dovresti scrivere la migrazione per rimuoverla prima a livello di database, quindi cambiare la tua mappatura DAL
gdbdable

74

È passato un po 'di tempo, ma lasciando una nota per gli altri. Ho ottenuto ciò che è necessario con un attributo e ho decorato i campi della mia classe di modello con quell'attributo come desidero.

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

Ho ottenuto l'aiuto di questi 2 articoli:

Cosa ho fatto:

Definisci attributo

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

Nel "OnModelCreating" del contesto

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

In SqlGenerator personalizzato

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

Quindi, nel costruttore Configurazione della migrazione, registrare il generatore SQL personalizzato.

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

6
Puoi persino farlo a livello globale senza mettere [SqlDefaultValue(DefaultValue = "getutcdate()")]su tutte le entità. 1) Rimuovi semplicemente modelBuilder.Conventions.Add( new AttributeToColumnAnnotationConvention<SqlDefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue)); 2) AggiungimodelBuilder.Properties().Where(x => x.PropertyType == typeof(DateTime)).Configure(c => c.HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed).HasColumnAnnotation("SqlDefaultValue", "getdate()"));
Daniel Skowroński

1
Dov'è lo SqlGenerator personalizzato per favore?
ʙᴀᴋᴇʀ ʙᴀᴋᴇʀ

3
Custom SqlGenerator è venuto da qui: andy.mehalick.com/2014/02/06/…
ravinsp

1
@ravinsp perché non personalizzare la classe MigrationCodeGenerator per avere la migrazione con le informazioni giuste anziché il codice SqlGenerator? Il codice SQL è l'ultimo passo ...
Alex

@Alex Vale la pena provare! Funzionerebbe anche e sarebbe più elegante dell'iniezione di codice SQL. Ma non sono sicuro della complessità della sostituzione di C # MigrationCodeGenerator.
Ravinsp,

73

Le risposte di cui sopra sono state di grande aiuto, ma hanno fornito solo una parte della soluzione. Il problema principale è che non appena si rimuove l'attributo Valore predefinito, il vincolo sulla colonna nel database non verrà rimosso. Quindi il valore predefinito precedente rimarrà comunque nel database.

Ecco una soluzione completa al problema, inclusa la rimozione dei vincoli SQL sulla rimozione degli attributi. Sto anche riutilizzando l' DefaultValueattributo nativo di .NET Framework .

uso

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

Perché ciò funzioni è necessario aggiornare i file IdentityModels.cs e Configuration.cs

File IdentityModels.cs

Aggiungi / aggiorna questo metodo nella tua 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);
}

File Configuration.cs

Aggiorna il tuo Configurationcostruttore di classi registrando il generatore Sql personalizzato, in questo modo:

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

Successivamente, aggiungi una classe di generatore SQL personalizzata (puoi aggiungerla al file Configuration.cs o a un file separato)

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
Questo approccio ha funzionato perfettamente per me. Un miglioramento che ho apportato è stato anche quello di ignorare Genera (CreateTableOperation createTableOperation) e Genera (AddColumnOperation addColumnOperation) con la stessa logica in modo da catturare anche quegli scenari. Controllo anche solo i valori. NewValue è nullo poiché volevo che il mio valore predefinito fosse una stringa vuota.
Delorian,

2
@Delorian Ho aggiornato la mia risposta, grazie per i tuoi commenti
Jevgenij Martynenko

1
Ho apportato una modifica al tuo post per supportare le istanze in cui un rollback fa cadere più di un vincolo. Lo script genererebbe un errore indicando che @con era già stato dichiarato. Ho creato una variabile privata per contenere un contatore e semplicemente incrementarlo. Ho anche modificato il formato del vincolo di rilascio in modo che corrisponda più strettamente a ciò che EF invia a SQL durante la creazione del vincolo. Ottimo lavoro su questo!
Brad

1
Grazie per la soluzione ma ho due problemi: 1. I nomi delle tabelle hanno bisogno di parentesi. 2. In aggiornamento nuovo valore non impostato e valore predefinito impostato invece!
Omid-RH,

1
Devo impostare l' [DatabaseGenerated(DatabaseGeneratedOption.Computed)]attributo? Se è così, perché? Nei miei test sembrava non avere alcun effetto tralasciandolo.
RamNow,

26

Le proprietà del modello non devono essere "proprietà automatiche" Anche se è più semplice. E l'attributo DefaultValue è in realtà solo metadati informativi. La risposta accettata qui è un'alternativa all'approccio del costruttore.

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; }
    }
}

vs.

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

    public int LengthInMeters { get; set; }        
}

Funzionerà solo per le applicazioni che creano e consumano dati usando questa classe specifica. Di solito questo non è un problema se il codice di accesso ai dati è centralizzato. Per aggiornare il valore in tutte le applicazioni è necessario configurare l'origine dati per impostare un valore predefinito. La risposta di Devi mostra come si può fare usando le migrazioni, sql o qualunque sia la lingua parlata dall'origine dati.


21
Nota: questo non imposterà un valore predefinito nel database . Altri programmi che non utilizzano la tua entità non otterranno quel valore predefinito.
Eric J.

Questa parte della risposta, ma non funziona se si inseriscono record in modi diversi da Entity Framework. Inoltre, se stai creando una nuova colonna non nulla su una tabella, ciò non ti darà la possibilità di impostare il valore predefinito per i record esistenti. @devi ha una preziosa aggiunta in basso.
d512,

Perché il tuo primo approccio è "corretto"? Incontrerai problemi involontari con l'approccio del costruttore?
WiteCastle,

una questione di opinione, e quelli cambiano :) E probabilmente no.
calebboyd,

2
In determinate circostanze, ho avuto problemi con l'approccio del costruttore; l'impostazione di un valore predefinito nel campo di supporto sembra essere la soluzione meno invasiva.
Lucent Fox,

11

Quello che ho fatto, ho inizializzato i valori nel costruttore dell'entità

Nota: gli attributi DefaultValue non imposteranno automaticamente i valori delle tue proprietà, devi farlo da solo


4
Il problema con il costruttore che imposta il valore è che EF eseguirà un aggiornamento del database durante il commit della transazione.
Luka,

Il modello prima supera i valori predefiniti in questo modo
Lucas

DefaultValue è una specie che vive in terre lontane, straniere, troppo timide per incontrare le tue proprietà da sola. Non emettere un suono, è facilmente spaventato - dovrebbe mai avvicinarsi abbastanza per sentirlo. +1 per affermare il per niente ovvio.
Risadinha,

8

Dopo il commento di @SedatKapanoglu, sto aggiungendo tutto il mio approccio che funziona, perché aveva ragione, il solo utilizzo dell'API fluente non funziona.

1- Creare un generatore di codice personalizzato e sovrascrivere Genera per 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- Assegna il nuovo generatore di codice:

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

3- Usa API fluenti per creare l'Annotazione:

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

Potresti vedere la mia soluzione completa, ho aggiunto tutta la mia implementazione di come funziona, grazie.
Denny Puig,

5

È semplice! Basta annotare con richiesto.

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

la migrazione risultante sarà:

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

Se si desidera true, modificare defaultValue su true nella migrazione prima di aggiornare il database


la migrazione generata automaticamente può quindi essere modificata e ti dimenticherai del valore predefinito
Fernando Torres

Semplice e funzionante, consente di aggiungere rapidamente una colonna a una tabella esistente con un vincolo di chiave esterna sulla nuova colonna. Grazie
po10cySA il

E se avessimo bisogno del valore predefinito true?
Christos Lytras,

5

Ammetto che il mio approccio sfugge all'intero concetto "Code First". Ma se hai la possibilità di cambiare il valore predefinito nella tabella stessa ... è molto più semplice delle lunghezze che devi passare sopra ... Sono troppo pigro per fare tutto quel lavoro!

Sembra quasi che l'idea originale dei poster avrebbe funzionato:

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

Pensavo che avessero appena commesso l'errore di aggiungere delle virgolette ... ma purtroppo tale intuitività. Gli altri suggerimenti erano semplicemente troppo per me (a condizione che avessi i privilegi necessari per entrare nella tabella e apportare le modifiche ... dove non tutti gli sviluppatori lo faranno in ogni situazione). Alla fine l'ho fatto alla vecchia maniera. Ho impostato il valore predefinito nella tabella di SQL Server ... Intendo davvero, già abbastanza! NOTA: ho ulteriormente testato la creazione di un database add-migrazione e aggiornamento e le modifiche sono state bloccate. inserisci qui la descrizione dell'immagine


1

Basta sovraccaricare il costruttore predefinito della classe Model e passare qualsiasi parametro rilevante che è possibile o meno utilizzare. In questo modo puoi facilmente fornire valori predefiniti per gli attributi. Di seguito è riportato un esempio.

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; }
    }

}

Questo suggerimento è totalmente logico lato client. Questo "funziona" purché interagirai solo con il database utilizzando l'applicazione. Non appena qualcuno vuole inserire un record manualmente o da un'altra applicazione, ti rendi conto del rovescio della medaglia che non esiste un'espressione predefinita nello schema e che non può essere adattata ad altri client. Sebbene non sia stato esplicitamente dichiarato, questo argomento legittimo implica l'obbligo per EF di creare una migrazione che inserisca un'espressione predefinita nella definizione di colonna.
Tom,

0

Supponiamo di avere un nome di classe chiamato Products e di avere un campo IsActive. hai solo bisogno di creare un costruttore:

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

Quindi il valore predefinito di IsActive è True!

Edite :

se vuoi farlo con SQL usa questo comando:

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

Semplicemente non era questa la domanda. Si trattava di vincoli di valore predefiniti nel database stesso.
Gábor,

4
@Hatef, la tua modifica si applica solo a EF Core. La domanda riguarda EF 6.
Michael,

3
Questo HasDefaultValueSql non è disponibile in EF6
tatigo

0

In EF core rilasciato il 27 giugno 2016 è possibile utilizzare l'API fluente per impostare il valore predefinito. Vai alla classe ApplicationDbContext, trova / crea il nome del metodo OnModelCreating e aggiungi la seguente API fluente.

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

0

In .NET Core 3.1 è possibile effettuare le seguenti operazioni nella classe del modello:

    public bool? Active { get; set; } 

In DbContext OnModelCreating si aggiunge il valore predefinito.

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

        base.OnModelCreating(modelBuilder);
    }

Il risultato è il seguente nel database

inserisci qui la descrizione dell'immagine

Nota: se non hai nullable (bool?) Per la tua proprietà riceverai il seguente avviso

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

Ho scoperto che usare l'inizializzatore di proprietà automatico sulla proprietà di entità è sufficiente per portare a termine il lavoro.

Per esempio:

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

2
Penseresti che funzionerebbe, prima con il codice. Ma non è stato per me con EF 6.2.
user1040323

-4

Hmm ... Prima faccio DB, e in quel caso, in realtà è molto più semplice. EF6 giusto? Basta aprire il modello, fare clic con il pulsante destro del mouse sulla colonna per cui si desidera impostare un valore predefinito, scegliere proprietà e verrà visualizzato un campo "Valore predefinito". Basta compilare e salvare. Imposta il codice per te.

Il tuo chilometraggio può variare in base al codice, tuttavia, non ho lavorato con quello.

Il problema con molte altre soluzioni, è che mentre potrebbero funzionare inizialmente, non appena ricostruirai il modello, esso eliminerà qualsiasi codice personalizzato inserito nel file generato dalla macchina.

Questo metodo funziona aggiungendo una proprietà aggiuntiva al file edmx:

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

E aggiungendo il codice necessario al costruttore:

public Thingy()
{
  this.Iteration = 1;

-5

Impostare il valore predefinito per la colonna nella tabella in MSSQL Server e nel codice di classe aggiungere l'attributo, in questo modo:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

per la stessa proprietà.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.