Entity Framework 6 Code first Valor predeterminado

204

¿Hay alguna forma "elegante" de dar a la propiedad específica un valor predeterminado?

Tal vez por DataAnnotations, algo así como:

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

Gracias.

marino-krk
fuente
Tal vez intente en el constructor this.Active = true;? Creo que el valor de la base de datos tendrá prioridad cuando se recupere, pero tenga cuidado si se realiza una nueva incorporación y luego se adjunta una entidad para una actualización sin una recuperación primero, ya que el seguimiento de cambios podría ver esto como que desea actualizar el valor. Comente porque no he usado EF en mucho tiempo, y siento que esta es una toma en la oscuridad.
AaronLS
3
Gracias por la respuesta, he usado este método hasta ahora stackoverflow.com/a/5032578/2913441 pero pensé que tal vez hay una mejor manera.
marino-krk
2
public bool Inactive { get; set; }😉
Jesse Hufstetler
como dicen los documentos de Microsoft "No puede establecer un valor predeterminado usando Anotaciones de datos".
hmfarimani
Consulte https://stackoverflow.com/a/59551802/8403632
shalitha senanayaka

Respuestas:

167

Puede hacerlo editando manualmente la primera migración de código:

public override void Up()
{    
   AddColumn("dbo.Events", "Active", c => c.Boolean(nullable: false, defaultValue: true));
} 
gdbdable
fuente
66
No estoy seguro de que esto funcionará si OP no creado especialmente Activea truela hora de crear un Eventobjeto así. El valor predeterminado siempre estará falseen una propiedad bool no anulable, por lo que, a menos que se cambie, esto es lo que el marco de la entidad guardará en la base de datos. ¿O me estoy perdiendo algo?
GFoley83
66
@ GFoley83, sí, tienes razón. Este método solo agrega restricción predeterminada a nivel de base de datos. Para una solución completa, también debe asignar el valor predeterminado en el constructor de la entidad o usar la propiedad con el campo respaldado como se muestra en la respuesta anterior
gdbdable
1
Esto funciona para los tipos base. Para algo como DATETIMEOFFSET, use, defaultValueSql: "SYSDATETIMEOFFSET", y NO el defaultValue como este defaultValue: System.DateTimeOffset.Now, se resolverá en una cadena del valor actual de fecha y hora del sistema.
OzBob
3
AFAIK sus cambios manuales se perderán en caso de volver a
andamiar
1
@ninbit, creo que primero debe escribir la migración para eliminarla a nivel de la base de datos, luego cambiar su mapeo DAL
gdbdable
74

Ha pasado un tiempo, pero dejando una nota para los demás. Logré lo que se necesita con un atributo y decoré mis campos de clase de modelo con ese atributo como quiero.

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

Obtuve la ayuda de estos 2 artículos:

Lo que hice:

Definir atributo

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

En el "OnModelCreating" del contexto

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

En el SqlGenerator personalizado

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

Luego, en el constructor Configuración de migración, registre el generador SQL personalizado.

SetSqlGenerator("System.Data.SqlClient", new CustomMigrationSqlGenerator());
ravinsp
fuente
66
Incluso puedes hacerlo globalmente sin poner [SqlDefaultValue(DefaultValue = "getutcdate()")]en cada entidad. 1) Simplemente elimine modelBuilder.Conventions.Add( new AttributeToColumnAnnotationConvention<SqlDefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue)); 2) AgregarmodelBuilder.Properties().Where(x => x.PropertyType == typeof(DateTime)).Configure(c => c.HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed).HasColumnAnnotation("SqlDefaultValue", "getdate()"));
Daniel Skowroński
1
¿Dónde está el SqlGenerator personalizado, por favor?
ᴍᴀᴛᴛ ʙᴀᴋᴇʀ
3
Custom SqlGenerator vino de aquí: andy.mehalick.com/2014/02/06/…
ravinsp
1
@ravinsp ¿por qué no personalizar la clase MigrationCodeGenerator para tener la migración con la información correcta en lugar del código SqlGenerator? El código SQL es el último paso ...
Alex
@Alex Vale la pena intentarlo! Eso también funcionaría y sería más elegante que inyectar código SQL. Pero no estoy seguro de la complejidad de anular el C # MigrationCodeGenerator.
ravinsp
73

Las respuestas anteriores realmente ayudaron, pero solo entregaron parte de la solución. El problema principal es que tan pronto como elimine el atributo Valor predeterminado, la restricción en la columna de la base de datos no se eliminará. Por lo tanto, el valor predeterminado anterior permanecerá en la base de datos.

Aquí hay una solución completa al problema, incluida la eliminación de restricciones de SQL en la eliminación de atributos. También estoy reutilizando el .NET Framework nativoDefaultValue atributo .

Uso

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

Para que esto funcione, necesita actualizar IdentityModels.cs y Configuration.cs archivos

Archivo IdentityModels.cs

Agregue / actualice este método en su ApplicationDbContextclase

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

Archivo Configuration.cs

Actualice su Configurationconstructor de clases registrando un generador SQL personalizado, como este:

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

A continuación, agregue la clase de generador de SQL personalizado (puede agregarlo al archivo Configuration.cs o a un archivo separado)

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;
    }
}
Jevgenij Martynenko
fuente
2
Este enfoque funcionó perfectamente para mí. Una mejora que hice fue también anular Generate (CreateTableOperation createTableOperation) y Generate (AddColumnOperation addColumnOperation) con la misma lógica para que esos escenarios también se capturen. También solo verifico los valores. NewValue es nulo ya que quería que mi valor predeterminado fuera una cadena vacía.
Delorian
2
@Delorian He actualizado mi respuesta, gracias por sus comentarios
Jevgenij Martynenko
1
He realizado una edición en su publicación para admitir instancias donde una reversión está eliminando más de una restricción. Su script arrojaría un error que indica que @con ya fue declarado. Creé una variable privada para contener un contador y simplemente incrementarlo. También cambié el formato de la restricción de caída para que coincida más estrechamente con lo que EF envía a SQL al crear la restricción. Gran trabajo en esto!
Brad
1
Gracias por la solución, pero tengo dos problemas: 1. Los nombres de tabla necesitan paréntesis. 2. En la actualización, el nuevo valor no está configurado y el valor predeterminado está configurado en su lugar.
Omid-RH
1
¿Necesito establecer el [DatabaseGenerated(DatabaseGeneratedOption.Computed)]atributo? Si es así, ¿por qué? En mis pruebas parecía no tener ningún efecto dejándolo fuera.
RamNow
26

Las propiedades de su modelo no tienen que ser 'propiedades automáticas' aunque eso sea más fácil. Y el atributo DefaultValue es realmente solo metadatos informativos. La respuesta aceptada aquí es una alternativa al enfoque del constructor.

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

Esto solo funcionará para aplicaciones que crean y consumen datos utilizando esta clase específica. Por lo general, esto no es un problema si el código de acceso a datos está centralizado. Para actualizar el valor en todas las aplicaciones, debe configurar el origen de datos para establecer un valor predeterminado. La respuesta de Devi muestra cómo se puede hacer usando migraciones, sql o cualquier idioma que hable su fuente de datos.

calebboyd
fuente
21
Nota: Esto no establecerá un valor predeterminado en la base de datos . Otros programas que no usan su entidad no obtendrán ese valor predeterminado.
Eric J.
Esta parte de la respuesta, pero no funciona si está insertando registros de otra manera que no sea a través de Entity Framework. Además, si está creando una nueva columna no nula en una tabla, esto no le dará la capacidad de establecer el valor predeterminado para los registros existentes. @devi tiene una valiosa adición a continuación.
d512
¿Por qué es su primer enfoque la forma "correcta"? ¿Te encontrarás con problemas no deseados con el enfoque de constructor?
WiteCastle
una cuestión de opinión, y esos cambios :) Y probablemente no.
calebboyd
2
En ciertas circunstancias, he tenido problemas con el enfoque de constructor; establecer un valor predeterminado en el campo de respaldo parece ser la solución menos invasiva.
Lucent Fox
11

Lo que hice, inicialicé valores en el constructor de la entidad

Nota: los atributos DefaultValue no establecerán los valores de sus propiedades automáticamente, debe hacerlo usted mismo

Sameh Deabes
fuente
44
El problema con el constructor que establece el valor es que EF hará una actualización de la base de datos al confirmar la transacción.
Luka
El modelo primero crea los valores predeterminados de esta manera
Lucas
DefaultValue es una especie que vive en tierras lejanas, extranjeras, demasiado tímidas para encontrar sus propiedades por sí misma. No haga ningún sonido, se asusta fácilmente, en caso de que se acerque lo suficiente como para escucharlo. +1 por afirmar lo nada obvio.
Risadinha
8

Después del comentario de @SedatKapanoglu, estoy agregando todo mi enfoque que funciona, porque tenía razón, solo usar la API fluida no funciona.

1- Crear generador de código personalizado y anular Generar para un modelo de columna.

   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- Asigna el nuevo generador de código:

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

3- Usa una API fluida para crear la Anotación:

public static void Configure(DbModelBuilder builder){    
builder.Entity<Company>().Property(c => c.Status).HasColumnAnnotation("Default", 0);            
}
Denny Puig
fuente
Por favor, puede ver mi solución completa, agregué toda mi implementación de cómo funciona, gracias.
Denny Puig
5

¡Es sencillo! Solo anote con requerido.

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

La migración resultante será:

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

Si desea verdadero, cambie el valor predeterminado a verdadero en la migración antes de actualizar la base de datos

Marisol Gutiérrez
fuente
la migración autogenerada se puede cambiar y se olvidará del valor predeterminado
Fernando Torres
Simple y funciona, ayuda a agregar rápidamente una columna a una tabla existente con una restricción de clave externa en la nueva columna. Gracias
po10cySA
¿Y qué pasa si necesitamos que sea el valor predeterminado true?
Christos Lytras
5

Admito que mi enfoque escapa al concepto completo de "Código Primero". Pero si tiene la capacidad de cambiar el valor predeterminado en la tabla en sí ... es mucho más simple que las longitudes que tiene que pasar arriba ... ¡Soy demasiado vago para hacer todo ese trabajo!

Casi parece que la idea original de los carteles funcionaría:

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

Pensé que simplemente cometieron el error de agregar citas ... pero desgraciadamente no hay tanta intuición. Las otras sugerencias fueron demasiado para mí (dado que tengo los privilegios necesarios para entrar en la mesa y hacer los cambios ... donde no todos los desarrolladores lo harán en cada situación). Al final lo hice a la antigua usanza. Configuré el valor predeterminado en la tabla de SQL Server ... Quiero decir, ¡ya es suficiente! NOTA: Realicé una prueba adicional haciendo una migración de bases de datos y actualizaciones y los cambios se atascaron. ingrese la descripción de la imagen aquí

Anthony Griggs
fuente
1

Simplemente sobrecargue el constructor predeterminado de la clase Modelo y pase cualquier parámetro relevante que pueda o no usar. Con esto, puede proporcionar fácilmente valores predeterminados para los atributos. A continuación se muestra un ejemplo.

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

}
Bappa Malakar
fuente
Esta sugerencia es totalmente lógica del lado del cliente. Esto "funciona" siempre que solo interactúe con la base de datos utilizando la aplicación. Tan pronto como alguien quiera insertar un registro manualmente o desde otra aplicación, se da cuenta de la desventaja de que no hay una expresión predeterminada en el esquema y esto no puede escalar a otros clientes. Aunque no se indicó explícitamente, este tema legítimo implica el requisito de obtener EF para crear una migración que coloque una expresión predeterminada en la definición de columna.
Tom
0

Consideremos que tiene un nombre de clase llamado Productos y que tiene un campo IsActive. solo necesitas un constructor de creación:

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

¡Entonces su valor predeterminado de IsActive es True!

Edite :

si quieres hacer esto con SQL usa este comando:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.IsActive)
        .HasDefaultValueSql("true");
}
Odio
fuente
Simplemente esta no era la pregunta. Se trataba de restricciones de valor por defecto en la base de datos en sí.
Gábor
44
@Hatef, su edición solo se aplica a EF Core. La pregunta es sobre EF 6.
Michael
3
Este HasDefaultValueSql no está disponible en EF6
tatigo
0

En EF core, lanzado el 27 de junio de 2016, puede usar una API fluida para establecer el valor predeterminado. Vaya a la clase ApplicationDbContext, busque / cree el nombre del método OnModelCreating y agregue la siguiente API fluida.

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

En .NET Core 3.1 puede hacer lo siguiente en la clase de modelo:

    public bool? Active { get; set; } 

En DbContext OnModelCreating agrega el valor predeterminado.

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

        base.OnModelCreating(modelBuilder);
    }

Resultando en lo siguiente en la base de datos

ingrese la descripción de la imagen aquí

Nota: Si no tiene nullable (bool?) Para su propiedad, recibirá la siguiente advertencia

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'.
Rugido Jørstad
fuente
-3

Descubrí que solo usando Auto-Property Initializer en la propiedad de la entidad es suficiente para hacer el trabajo.

Por ejemplo:

public class Thing {
    public bool IsBigThing{ get; set; } = false;
}
Velyo
fuente
2
Uno pensaría que esto funcionaría, con el código primero. Pero no fue para mí con EF 6.2.
user1040323
-4

Hmm ... hago DB primero, y en ese caso, esto es realmente mucho más fácil. EF6 ¿verdad? Simplemente abra su modelo, haga clic derecho en la columna para la que desea establecer un valor predeterminado, elija propiedades y verá un campo "Valor predeterminado". Solo complete eso y ahorre. Configurará el código para usted.

Sin embargo, su kilometraje puede variar en el código primero, no he trabajado con eso.

El problema con muchas otras soluciones es que, si bien pueden funcionar inicialmente, tan pronto como reconstruya el modelo, arrojará cualquier código personalizado que haya insertado en el archivo generado por la máquina.

Este método funciona agregando una propiedad adicional al archivo edmx:

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

Y agregando el código necesario al constructor:

public Thingy()
{
  this.Iteration = 1;
Acorndog
fuente
-5

Establezca el valor predeterminado para la columna en la tabla en el servidor MSSQL y en el atributo de agregar código de clase, de esta manera:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

por la misma propiedad

ngochoaitn
fuente