Entidade Estrutura 6 Código primeiro Valor padrão


204

existe uma maneira "elegante" de atribuir um valor padrão a uma propriedade específica?

Talvez por DataAnnotations, algo como:

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

Obrigado.


Talvez tente no construtor this.Active = true;? Eu acho que o valor do banco de dados terá precedência ao buscar, mas tome cuidado se for necessário anexar uma entidade para uma atualização sem uma busca primeiro, pois o rastreamento de alterações pode ver isso como você deseja atualizar o valor. Comente porque não uso a EF há muito tempo e sinto que isso é um tiro no escuro.
AaronLS

3
Obrigado pela resposta, eu usei esse método até agora stackoverflow.com/a/5032578/2913441, mas pensei que talvez haja uma maneira melhor.
marino-krk

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

como os documentos da Microsoft dizem "Você não pode definir um valor padrão usando as Anotações de Dados".
Hmfarimani

Por favor,
refira

Respostas:


167

Você pode fazer isso editando manualmente a primeira migração do código:

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

6
Não tenho certeza se isso funcionará se o OP também não estiver definido especialmente Activeao truecriar um Eventobjeto. O valor padrão sempre está falseem uma propriedade bool não anulável; portanto, a menos que seja alterado, é isso que a estrutura da entidade salvará no banco de dados. Ou eu estou esquecendo de alguma coisa?
GFoley83 17/02/2015

6
@ GFoley83, sim, você está certo. Este método adiciona apenas restrição padrão no nível do banco de dados. Para solução completa você também precisa valor padrão atribuir no construtor da entidade ou propriedade uso com campo apoiado como mostrado na resposta acima
gdbdable

1
Isso funciona para tipos de base. Para algo como DATETIMEOFFSET, use o defaultValueSql: "SYSDATETIMEOFFSET" e NÃO o defaultValue como este defaultValue: System.DateTimeOffset.Now, será resolvido para uma sequência do valor atual do datetimeoffset do sistema.
OzBob

3
AFAIK suas alterações manuais vai se perder em caso de re-andaimes a migração
Alexander Powolozki

1
@ninbit, acho que você deve escrever a migração para removê-la primeiro no nível do banco de dados e depois alterar o mapeamento DAL
gdbdable

74

Já faz um tempo, mas deixando um recado para os outros. Consegui o que é necessário com um atributo e decorei meus campos de classe de modelo com esse atributo, conforme desejado.

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

Obtenha a ajuda destes 2 artigos:

O que eu fiz:

Definir atributo

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

No "OnModelCreating" do contexto

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

No SqlGenerator personalizado

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

Em seguida, no construtor Migration Configuration, registre o gerador SQL personalizado.

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

6
Você pode fazê-lo globalmente sem colocar [SqlDefaultValue(DefaultValue = "getutcdate()")]todas as entidades. 1) Simplesmente remova modelBuilder.Conventions.Add( new AttributeToColumnAnnotationConvention<SqlDefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue)); 2) AddmodelBuilder.Properties().Where(x => x.PropertyType == typeof(DateTime)).Configure(c => c.HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed).HasColumnAnnotation("SqlDefaultValue", "getdate()"));
Daniel Skowroński /

1
Onde está o SqlGenerator personalizado, por favor?
ᴍᴀᴛᴛ ʙᴀᴋᴇʀ

3
O SqlGenerator personalizado veio daqui: andy.mehalick.com/2014/02/06/…
ravinsp

1
@ravinsp Por que não personalizar a classe MigrationCodeGenerator para ter a migração com as informações corretas, em vez do código SqlGenerator? O código SQL é o último passo ...
Alex

@Alex Worth tentando! Isso também funcionaria e seria mais elegante do que injetar código SQL. Mas não tenho certeza sobre a complexidade de substituir o C # MigrationCodeGenerator.
ravinsp

73

As respostas acima realmente ajudaram, mas apenas forneceram parte da solução. O principal problema é que, assim que você remover o atributo Valor padrão, a restrição na coluna no banco de dados não será removida. Portanto, o valor padrão anterior ainda permanecerá no banco de dados.

Aqui está uma solução completa para o problema, incluindo a remoção de restrições SQL na remoção de atributos. Também estou reutilizando o DefaultValueatributo nativo do .NET Framework .

Uso

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

Para que isso funcione, é necessário atualizar os arquivos IdentityModels.cs e Configuration.cs

Arquivo IdentityModels.cs

Adicione / atualize este método em sua 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);
}

Arquivo Configuration.cs

Atualize seu Configurationconstrutor de classe registrando o gerador Sql personalizado, desta forma:

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

Em seguida, adicione a classe de gerador Sql personalizada (você pode adicioná-lo ao arquivo Configuration.cs ou a um arquivo 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;
    }
}

2
Essa abordagem funcionou perfeitamente para mim. Um aprimoramento que fiz foi substituir também Generate (CreateTableOperation createTableOperation) e Generate (AddColumnOperation addColumnOperation) pela mesma lógica, para que esses cenários também sejam capturados. Também verifiquei apenas valores.NewValue é nulo, pois queria que meu padrão fosse uma sequência vazia.
Delorian

2
@Delorian Eu atualizei a minha resposta, obrigado por seus comentários
Jevgenij Martynenko

1
Fiz uma edição em sua postagem para dar suporte a instâncias em que uma reversão está eliminando mais de uma restrição. Seu script geraria um erro informando que @con já foi declarado. Criei uma variável privada para armazenar um contador e simplesmente incrementá-lo. Também alterei o formato da restrição de descarte para corresponder melhor ao que o EF envia ao SQL ao criar a restrição. Bom trabalho nisso!
25416 Brad

1
Obrigado pela solução, mas tenho dois problemas: 1. Os nomes das tabelas precisam de colchetes. 2. Na atualização, novo valor não definido e valor padrão definido!
Omid-RH

1
Preciso definir o [DatabaseGenerated(DatabaseGeneratedOption.Computed)]atributo? Se sim, por quê? Nos meus testes, parecia não ter efeito deixar de fora.
21718 RamNow

26

As propriedades do seu modelo não precisam ser 'propriedades automáticas', embora isso seja mais fácil. E o atributo DefaultValue é realmente apenas metadados informativos. A resposta aceita aqui é uma alternativa à abordagem do construtor.

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

Isso funcionará apenas para aplicativos que criam e consomem dados usando essa classe específica. Normalmente, isso não é um problema se o código de acesso a dados estiver centralizado. Para atualizar o valor em todos os aplicativos, você precisa configurar a fonte de dados para definir um valor padrão. A resposta da Devi mostra como isso pode ser feito usando migrações, sql ou qualquer idioma que sua fonte de dados fale.


21
Nota: Isso não definirá um valor padrão no banco de dados . Outros programas que não usam sua entidade não obterão esse valor padrão.
Eric J.

Esta parte da resposta, mas não funcionará se você estiver inserindo registros de outras maneiras que não o Entity Framework. Além disso, se você estiver criando uma nova coluna não nula em uma tabela, isso não permitirá que você defina o valor padrão para os registros existentes. O @devi tem uma adição valiosa abaixo.
d512 24/02

Por que sua primeira abordagem é a maneira "correta"? Você encontrará problemas não intencionais com a abordagem do construtor?
WiteCastle

uma questão de opinião, e isso muda :) E provavelmente não.
calebboyd

2
Sob certas circunstâncias, tive problemas com a abordagem do construtor; definir um valor padrão no campo de apoio parece ser a solução menos invasiva.
Lucent Fox

11

O que eu fiz, eu inicializei valores no construtor da entidade

Nota: Os atributos DefaultValue não definirão os valores de suas propriedades automaticamente, você deve fazer isso sozinho


4
O problema com o construtor que define o valor é que o EF fará uma atualização no banco de dados ao confirmar a transação.
Luka

O modelo primeiro cria os valores padrão dessa maneira
Lucas

O DefaultValue é uma espécie que vive em terras distantes e estrangeiras, muito tímidas para encontrar suas propriedades por si só. Não faça barulho, ele é facilmente assustado - deve chegar perto o suficiente para ouvi-lo. +1 por declarar o nada óbvio.
Risadinha

8

Após o comentário do @SedatKapanoglu, estou adicionando toda a minha abordagem que funciona, porque ele estava certo, apenas o uso da API fluente não funciona.

1- Crie um gerador de código personalizado e substitua Gerar para um 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- Atribua o novo gerador de código:

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

3- Use API fluente para criar a anotação:

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

Por favor, pude ver minha solução completa, adicionei toda a minha implementação de como funciona, obrigado.
Denny Puig

5

É simples! Apenas anote com o necessário.

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

a migração resultante será:

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

Se você quiser true, altere o defaultValue para true na migração antes de atualizar o banco de dados


a migração gerada automaticamente pode ser alterada e você irá esquecer o valor padrão
Fernando Torres

Simples e funciona, ajuda a adicionar rapidamente uma coluna a uma tabela existente com uma restrição de chave estrangeira na nova coluna. obrigado
po10cySA 22/01

E se precisarmos que o valor padrão seja true?
Christos Lytras

5

Admito que minha abordagem escapa a todo o conceito "Code First". Mas se você tem a capacidade de alterar apenas o valor padrão na própria tabela ... é muito mais simples do que os comprimentos que você precisa passar acima ... Estou com preguiça de fazer todo esse trabalho!

Quase parece que a ideia original dos pôsteres funcionaria:

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

Eu pensei que eles apenas cometeram o erro de adicionar aspas ... mas, infelizmente, não existe essa intuição. As outras sugestões foram demais para mim (desde que eu tenha os privilégios necessários para entrar na mesa e fazer as alterações ... onde nem todos os desenvolvedores estarão em todas as situações). No final, eu fiz da maneira antiga. Defino o valor padrão na tabela do SQL Server ... quero dizer, já chega! NOTA: Além disso, testei a adição de um banco de dados de migração e atualização e as alterações travadas. insira a descrição da imagem aqui


1

Apenas sobrecarregue o construtor padrão da classe Model e passe qualquer parâmetro relevante que você possa ou não usar. Com isso, você pode facilmente fornecer valores padrão para atributos. Abaixo está um exemplo.

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

}

Essa sugestão é totalmente lógica do lado do cliente. Isso "funciona" desde que você só interaja com o banco de dados usando o aplicativo. Assim que alguém deseja inserir um registro manualmente ou de outro aplicativo, você percebe a desvantagem de que não há expressão padrão no esquema e isso não pode ser dimensionado para outros clientes. Embora não tenha sido explicitamente declarado, esse tópico legítimo implica o requisito para que o EF crie uma migração que coloque uma expressão padrão na definição da coluna.
Tom

0

Vamos considerar que você tem um nome de classe chamado Produtos e um campo IsActive. você só precisa de um construtor create:

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

Em seguida, seu valor padrão IsActive é True!

Edite :

se você quiser fazer isso com o SQL, use este comando:

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

Simplesmente não era essa a questão. Tratava-se de restrições de valor padrão no próprio banco de dados.
Gábor

4
@Hatef, sua edição se aplica apenas ao EF Core. A pergunta é sobre EF 6.
Michael

3
Este HasDefaultValueSql não está disponível no EF6
tatigo

0

No núcleo da EF lançado em 27 de junho de 2016, você pode usar a API fluente para definir o valor padrão. Vá para a classe ApplicationDbContext, encontre / crie o nome do método OnModelCreating e adicione a seguinte API fluente.

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

0

No .NET Core 3.1, você pode fazer o seguinte na classe de modelo:

    public bool? Active { get; set; } 

No DbContext OnModelCreating, você adiciona o valor padrão.

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

        base.OnModelCreating(modelBuilder);
    }

Resultando no seguinte no banco de dados

insira a descrição da imagem aqui

Nota: Se você não tiver nulo (bool?) Para sua propriedade, receberá o seguinte aviso

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

Descobri que apenas o uso do Inicializador automático de propriedades na propriedade da entidade é suficiente para realizar o trabalho.

Por exemplo:

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

2
Você acha que isso funcionaria, com o código primeiro. Mas isso não aconteceu para mim com a EF 6.2.
user1040323

-4

Hmm ... eu faço DB primeiro, e nesse caso, isso é realmente muito mais fácil. EF6 certo? Basta abrir o modelo, clicar com o botão direito do mouse na coluna para a qual você deseja definir um padrão, escolher propriedades e você verá um campo "DefaultValue". Basta preencher e salvar. Ele irá configurar o código para você.

Sua milhagem pode variar primeiro no código, mas não trabalhei com isso.

O problema com muitas outras soluções é que, embora possam funcionar inicialmente, assim que você reconstruir o modelo, ele lançará qualquer código personalizado inserido no arquivo gerado pela máquina.

Este método funciona adicionando uma propriedade extra ao arquivo edmx:

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

E adicionando o código necessário ao construtor:

public Thingy()
{
  this.Iteration = 1;

-5

Defina o valor padrão para a coluna na tabela no MSSQL Server e, no código da classe, adicione o atributo, assim:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

para a mesma propriedade.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.