Entity Framework 6 Kod pierwszy Wartość domyślna


203

czy istnieje „elegancki” sposób nadania określonej właściwości wartości domyślnej?

Może przez DataAnnotations coś takiego:

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

Dziękuję Ci.


Może spróbuj w konstruktorze this.Active = true;? Myślę, że wartość DB będzie miała pierwszeństwo podczas pobierania, ale uważaj, jeśli new'ing, a następnie dołączenie encji do aktualizacji bez pobierania najpierw, ponieważ śledzenie zmian może postrzegać to jako chęć aktualizacji wartości. Skomentuj, ponieważ nie używałem EF od dawna i wydaje mi się, że to strzał w ciemność.
AaronLS,

3
Dziękuję za odpowiedź. Dotychczas stosowałem tę metodę stackoverflow.com/a/5032578/2913441, ale pomyślałem, że może być lepszy sposób.
marino-krk

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

jak mówią dokumenty Microsoft: „Nie można ustawić wartości domyślnej za pomocą adnotacji danych”.
hmfarimani

Proszę
odnieść się

Odpowiedzi:


166

Możesz to zrobić, ręcznie edytując pierwszą migrację kodu:

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

6
Nie jestem pewien, że to będzie działać, jeśli PO nie specjalnie ustawione Activedo truepodczas tworzenia Eventobiektu, jak również. Domyślną wartością zawsze jest falsewłaściwość bool, która nie ma wartości null, więc jeśli nie zostanie zmieniona, to właśnie struktura encji zapisze w db. A może coś mi brakuje?
GFoley83,

6
@ GFoley83, tak, masz rację. Ta metoda dodaje domyślne ograniczenie tylko na poziomie bazy danych. Aby uzyskać kompletne rozwiązanie, należy również przypisać wartość domyślną w konstruktorze obiektu lub użyć właściwości z polem kopii
zapasowej,

1
Działa to dla typów podstawowych. W przypadku czegoś takiego jak DATETIMEOFFSET użyj parametru defaultValueSql: „SYSDATETIMEOFFSET”, a NIE wartości defaultValue, ponieważ ta wartość defaultValue: System.DateTimeOffset.Now, zostanie przetłumaczona na ciąg bieżącej wartości systemowej wartości datetimeoffset.
OzBob

3
AFAIK twoje ręczne zmiany zostaną utracone w przypadku ponownego rusztowania migracji
Alexander Powolozki

1
@ninbit Myślę, że należy najpierw napisać migrację, aby usunąć ją na poziomie bazy danych, a następnie zmienić mapowanie DAL
gdbdable

74

Minęło trochę czasu, ale pozostawiając notatkę innym. Osiągnąłem to, co jest potrzebne z atrybutem i ozdobiłem pola mojej klasy modelem tym atrybutem, jak chcę.

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

Otrzymałem pomoc z tych 2 artykułów:

Co ja zrobiłem:

Zdefiniuj atrybut

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

W „OnModelCreating” kontekstu

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

W niestandardowym SqlGenerator

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

Następnie w konstruktorze konfiguracji migracji zarejestruj niestandardowy generator SQL.

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

6
Możesz to zrobić globalnie, bez nakładania [SqlDefaultValue(DefaultValue = "getutcdate()")]każdego elementu. 1) Po prostu usuń modelBuilder.Conventions.Add( new AttributeToColumnAnnotationConvention<SqlDefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue)); 2) DodajmodelBuilder.Properties().Where(x => x.PropertyType == typeof(DateTime)).Configure(c => c.HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed).HasColumnAnnotation("SqlDefaultValue", "getdate()"));
Daniel Skowroński

1
Gdzie jest niestandardowy generator SqlGenerator?
24 ʙᴀᴋᴇʀ

3
Niestandardowy SqlGenerator pochodził stąd: andy.mehalick.com/2014/02/06/…
ravinsp

1
@ravinsp, dlaczego nie dostosować klasy MigrationCodeGenerator, aby migracja zawierała właściwe informacje zamiast kodu SqlGenerator? Kod SQL jest ostatnim krokiem ...
Alex

@Alex Warto spróbować! To również działałoby i byłoby bardziej eleganckie niż wstrzykiwanie kodu SQL. Ale nie jestem pewien złożoności przesłonięcia C # MigrationCodeGenerator.
ravinsp

73

Powyższe odpowiedzi naprawdę pomogły, ale dostarczyły tylko część rozwiązania. Głównym problemem jest to, że jak tylko usuniesz atrybut Wartość domyślna, ograniczenie kolumny w bazie danych nie zostanie usunięte. Tak więc poprzednia wartość domyślna pozostanie w bazie danych.

Oto pełne rozwiązanie problemu, w tym usunięcie ograniczeń SQL przy usuwaniu atrybutów. Używam również natywnego .NET FrameworkDefaultValue atrybutu .

Stosowanie

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

Aby to zadziałało, musisz zaktualizować IdentityModels.cs i Configuration.cs pliki

Plik IdentityModels.cs

Dodaj / zaktualizuj tę metodę w swojej ApplicationDbContextklasie

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

Plik Configuration.cs

Zaktualizuj Configurationkonstruktor klasy, rejestrując niestandardowy generator Sql:

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

Następnie dodaj niestandardową klasę generatora Sql (możesz dodać ją do pliku Configuration.cs lub osobnego pliku)

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
To podejście działało dla mnie idealnie. Jednym z ulepszeń, które wprowadziłem, było zastąpienie opcji Generuj (CreateTableOperation createTableOperation) i Generuj (AddColumnOperation addColumnOperation) z tą samą logiką, aby te scenariusze zostały również przechwycone. Sprawdzam również tylko wartości. NewValue ma wartość NULL, ponieważ chciałem, aby mój domyślny ciąg znaków był pusty.
Delorian,

2
@Delorian Zaktualizowałem swoją odpowiedź, dziękuję za komentarze
Jevgenij Martynenko,

1
Wprowadziłem edycję Twojego postu, aby wesprzeć przypadki, w których wycofanie usuwa więcej niż jedno ograniczenie. Twój skrypt zgłosi błąd informujący, że @con został już zadeklarowany. Utworzyłem zmienną prywatną, która przechowuje licznik i po prostu zwiększa go. Zmieniłem również format ograniczenia upuszczania, aby ściślej pasowało do tego, co EF wysyła do SQL podczas tworzenia ograniczenia. Świetna robota!
Brad

1
Dzięki za rozwiązanie, ale mam dwa problemy: 1. Nazwy tabel wymagają nawiasów. 2. W aktualizacji nie ustawiono nowej wartości i zamiast tego ustawiono wartość domyślną!
Omid-RH

1
Czy potrzebuję ustawić [DatabaseGenerated(DatabaseGeneratedOption.Computed)]atrybut? Jeśli tak, to dlaczego? W moich testach wydawało się, że nie ma żadnego efektu, pomijając to.
RamNow

26

Właściwości modelu nie muszą być „właściwościami automatycznymi”, chociaż jest to łatwiejsze. A atrybut DefaultValue jest tak naprawdę tylko informacyjnymi metadanymi. Przyjęta tutaj odpowiedź jest jedną z alternatyw dla podejścia konstruktora.

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

Będzie to działać tylko w przypadku aplikacji tworzących i konsumujących dane przy użyciu tej konkretnej klasy. Zwykle nie stanowi to problemu, jeśli kod dostępu do danych jest scentralizowany. Aby zaktualizować wartość we wszystkich aplikacjach, należy skonfigurować źródło danych, aby ustawić wartość domyślną. Odpowiedź Devi pokazuje, jak można to zrobić za pomocą migracji, sql lub dowolnego języka, w którym mówi twoje źródło danych.


21
Uwaga: To nie ustawi wartości domyślnej w bazie danych . Inne programy nieużywające twojej jednostki nie uzyskają tej wartości domyślnej.
Eric J.

Ta część odpowiedzi, ale nie działa, jeśli wstawiasz rekordy w sposób inny niż za pośrednictwem Entity Framework. Również jeśli tworzysz nową niepustą kolumnę w tabeli, nie da ci to możliwości ustawienia wartości domyślnej dla istniejących rekordów. @devi ma cenny dodatek poniżej.
d512,

Dlaczego Twoje pierwsze podejście jest „prawidłowe”? Czy napotkasz niezamierzone problemy z podejściem konstruktora?
WiteCastle

kwestia opinii, a one się zmieniają :) I prawdopodobnie nie.
calebboyd

2
W pewnych okolicznościach miałem problemy z podejściem konstruktora; ustawienie wartości domyślnej w polu podkładowym wydaje się być najmniej inwazyjnym rozwiązaniem.
Lucent Fox,

11

To, co zrobiłem, zainicjowałem wartości w konstruktorze encji

Uwaga: atrybuty DefaultValue nie ustawią wartości twoich właściwości automatycznie, musisz to zrobić sam


4
Problem z ustawieniem wartości przez konstruktor polega na tym, że EF wykona aktualizację bazy danych podczas zatwierdzania transakcji.
Luka,

W ten sposób model najpierw tworzy wartości domyślne
Lucas,

DefaultValue jest gatunkiem żyjącym w odległych, obcych krajach, zbyt nieśmiałym, aby sam mógł poznać twoje właściwości. Nie wydawaj dźwięku, łatwo go odstraszyć - jeśli kiedykolwiek zbliży się na tyle, aby go usłyszeć. +1 za stwierdzenie, że wcale nie jest oczywiste.
Risadinha

8

Po komentarzu @SedatKapanoglu dodałem całe moje podejście, które działa, ponieważ miał rację, samo używanie płynnego API nie działa.

1- Utwórz niestandardowy generator kodu i przesłoń Generuj dla modelu 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- Przypisz nowy generator kodu:

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

3- Użyj płynnego interfejsu API, aby utworzyć adnotację:

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

Proszę zobaczyć moje kompletne rozwiązanie. Dodałem całą implementację tego, jak to działa, dzięki.
Denny Puig,

5

To proste! Po prostu adnotuj z wymaganymi.

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

wynikowa migracja będzie:

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

Jeśli chcesz mieć wartość true, zmień wartość defaultValue na true w migracji przed aktualizacją bazy danych


migracja wygenerowana automatycznie może zostać zmieniona, a użytkownik zapomni o wartości domyślnej
Fernando Torres

Prosty i działa, pomaga szybko dodać kolumnę do istniejącej tabeli z ograniczeniem klucza obcego w nowej kolumnie. Dzięki
po10cySA

A jeśli potrzebujemy domyślnej wartości true?
Christos Lytras

5

Przyznaję, że moje podejście wymyka się całej koncepcji „Code First”. Ale jeśli masz możliwość zmiany domyślnej wartości w samej tabeli ... jest to o wiele prostsze niż długości, które musisz pokonać powyżej ... Jestem po prostu zbyt leniwy, aby wykonać całą tę pracę!

Wygląda na to, że oryginalny pomysł na plakaty zadziałałby:

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

Myślałem, że popełniali błąd, dodając cytaty ... ale niestety nie ma takiej intuicyjności. Inne sugestie były dla mnie po prostu za dużo (oczywiście, mam uprawnienia potrzebne do wejścia do tabeli i wprowadzenia zmian ... gdzie nie każdy programista zrobi to w każdej sytuacji). W końcu zrobiłem to po prostu w staromodny sposób. Ustawiam wartość domyślną w tabeli SQL Server ... Mam na myśli naprawdę, już dość! UWAGA: Następnie przetestowałem, wykonując migrację dodatków i aktualizację bazy danych, a zmiany utknęły. wprowadź opis zdjęcia tutaj


1

Po prostu przeciąż domyślnego konstruktora klasy Model i przekaż dowolny odpowiedni parametr, którego możesz użyć lub nie. Dzięki temu można łatwo podać wartości domyślne dla atrybutów. Poniżej znajduje się przykład.

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

}

Ta sugestia jest całkowicie logiczna po stronie klienta. To „działa”, o ile będziesz wchodził w interakcje z bazą danych tylko za pomocą aplikacji. Gdy tylko ktoś chce wstawić rekord ręcznie lub z innej aplikacji, zdajesz sobie sprawę, że wadą jest brak domyślnego wyrażenia w schemacie i nie można go skalować do innych klientów. Chociaż nie zostało to wyraźnie stwierdzone, ten uzasadniony temat implikuje wymóg, aby EF utworzył migrację, która umieszcza domyślne wyrażenie w definicji kolumny.
Tom

0

Rozważmy, że masz nazwę klasy o nazwie Produkty i masz pole IsActive. potrzebujesz konstruktora tworzenia:

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

Zatem domyślną wartością IsActive jest Prawda!

Edycja :

jeśli chcesz to zrobić za pomocą SQL, użyj tego polecenia:

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

Po prostu to nie było pytanie. Chodziło o ograniczenia wartości domyślnych w samej bazie danych.
Gábor

4
@Hatef, twoja zmiana dotyczy tylko EF Core. Pytanie dotyczy EF 6
Michael

3
To HasDefaultValueSql nie jest dostępne w
EF6

0

W rdzeniu EF wydanym 27 czerwca 2016 r. Możesz używać płynnego API do ustawiania wartości domyślnej. Przejdź do klasy ApplicationDbContext, znajdź / utwórz nazwę metody OnModelCreating i dodaj następujący płynny interfejs API.

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

0

W .NET Core 3.1 możesz wykonać następujące czynności w klasie modeli:

    public bool? Active { get; set; } 

W DbContext OnModelCreating dodajesz wartość domyślną.

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

        base.OnModelCreating(modelBuilder);
    }

W wyniku tego w bazie danych

wprowadź opis zdjęcia tutaj

Uwaga: jeśli nie masz wartości zerowej (bool?) Dla swojej właściwości, otrzymasz następujące ostrzeżenie

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

Przekonałem się, że wystarczy użyć Autoinicjatora właściwości dla właściwości encji, aby wykonać zadanie.

Na przykład:

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

2
Można by pomyśleć, że to zadziała, najpierw kod. Ale nie działało to dla mnie z EF 6.2.
user1040323,

-4

Hmm ... najpierw wykonuję DB, aw takim przypadku jest to o wiele łatwiejsze. EF6 prawda? Po prostu otwórz model, kliknij prawym przyciskiem myszy kolumnę, dla której chcesz ustawić wartość domyślną, wybierz właściwości, a zobaczysz pole „DefaultValue”. Po prostu wypełnij i zapisz. Ustawi dla ciebie kod.

Twój przebieg może się różnić w zależności od kodu, ale nie pracowałem z tym.

Problem z wieloma innymi rozwiązaniami polega na tym, że chociaż mogą one działać początkowo, to zaraz po przebudowaniu modelu wyrzucą one dowolny niestandardowy kod wstawiony do pliku generowanego maszynowo.

Ta metoda działa poprzez dodanie dodatkowej właściwości do pliku edmx:

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

I dodając niezbędny kod do konstruktora:

public Thingy()
{
  this.Iteration = 1;

-5

Ustaw wartość domyślną dla kolumny w tabeli na serwerze MSSQL, aw kodzie klasy dodaj atrybut, jak poniżej:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

dla tej samej nieruchomości.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.