Entity Framework 6 Перше значення за замовчуванням


203

чи є "елегантний" спосіб надати конкретній властивості значення за замовчуванням?

Можливо, завдяки DataAnnotations, щось на кшталт:

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

Дякую.


Може, спробувати в конструкторі this.Active = true;? Я думаю, що значення БД буде мати перевагу під час отримання, але обережно, якщо new'ing потім додає сутність для оновлення без вибору спочатку, оскільки відстеження змін може бачити це так, як ви хочете оновити значення. Прокоментуйте, тому що я давно не використовував EF, і мені здається, що це постріл у темряві.
AaronLS

3
Дякую за відповідь, я до цього часу використовував цей метод stackoverflow.com/a/5032578/2913441, але я подумав, що, можливо, є кращий спосіб.
marino-krk

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

як говорять документи Microsoft "Ви не можете встановити значення за замовчуванням за допомогою Анотацій даних".
hmfarimani

Будь ласка, референтуйте https://stackoverflow.com/a/59551802/8403632
shalitha senanayaka

Відповіді:


166

Ви можете це зробити, вручну відредагувавши код першого переміщення:

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

6
Я не впевнений, що це спрацює, якщо ОП спеціально не встановлено Activeпід trueчас створення Eventоб'єкта. Значення за замовчуванням завжди має бути falseна ненульовому властивості bool, тому, якщо не буде змінено, це те, що рамки сутності збережуть у db. Або я щось пропускаю?
GFoley83

6
@ GFoley83, так, ти маєш рацію. Цей метод додає обмеження за замовчуванням лише на рівні бази даних. Для повного рішення вам також потрібно призначити значення за замовчуванням у конструкторі сутності або використовувати властивість із резервним полем, як показано у відповіді вище
gdbdable

1
Це працює для базових типів. Для чогось типу DATETIMEOFFSET використовуйте, defaultValueSql: "SYSDATETIMEOFFSET", а НЕ defaultValue, оскільки ця defaultValue: System.DateTimeOffset.Now, буде вирішувати рядок поточного системного значення timetimesetset.
OzBob

3
AFAIK ваші зміни вручну втратяться у разі повторного переселення міграції
Олександр Поволозки

1
@ninbit Я думаю, ви повинні написати міграцію, щоб спочатку її видалити на рівні бази даних, а потім змінити DAL-картування
gdbdable

74

Минув деякий час, але залишав замітку для інших. Я домігся того, що потрібно за допомогою атрибута, і прикрасив свої атрибути класу моделей таким атрибутом, як я хочу.

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

Отримав допомогу з цих 2 статей:

Що я зробив:

Визначте атрибут

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

У "OnModelCreating" контексту

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

У користувацькому SqlGenerator

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

Потім в конструкторі конфігурації міграції зареєструйте спеціальний генератор SQL.

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

6
Ви навіть можете це зробити в усьому світі, не вкладаючи жодної [SqlDefaultValue(DefaultValue = "getutcdate()")]сутності. 1) Просто видаліть 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
Де користувацький SqlGenerator, будь ласка?
ᴍᴀᴛᴛ ʙᴀᴋᴇʀ

3
Спеціальні SqlGenerator прийшли звідси: andy.mehalick.com/2014/02/06/…
ravinsp

1
@ravinsp, чому б не налаштувати клас MigrationCodeGenerator так, щоб міграція мала правильну інформацію, а не код SqlGenerator? Код SQL - останній крок ...
Алекс

@Alex Варто спробувати! Це також спрацювало б і було б більш елегантним, ніж введення коду SQL. Але я не впевнений у складності зміни C # MigrationCodeGenerator.
ravinsp

73

Наведені вище відповіді справді допомогли, але лише доставили частину рішення. Основна проблема полягає в тому, що як тільки ви видалите атрибут значення за замовчуванням, обмеження стовпця в базі даних не буде видалено. Отже попереднє значення за замовчуванням все ще залишатиметься в базі даних.

Ось повне рішення проблеми, включаючи усунення обмежень SQL щодо видалення атрибутів. Я також повторно використовую вбудований DefaultValueатрибут .NET Framework .

Використання

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

Для цього вам потрібно оновити файли IdentityModels.cs і Configuration.cs

Файл IdentityModels.cs

Додайте / оновіть цей метод у своєму ApplicationDbContextкласі

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

Файл Configuration.cs

Оновіть свій Configurationконструктор класів, зареєструвавши власний генератор Sql, як це:

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

Потім додайте спеціальний клас генератора Sql (ви можете додати його у файл Configuration.cs або окремий файл)

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
Такий підхід для мене прекрасно працював. Одним із покращень, які я зробив, було також переосмислення Generate (CreateTableOperation createTableOperation) та Generate (AddColumnOperation addColumnOperation) з тією ж логікою, щоб ці сценарії також потрапляли. Я також перевіряю лише значення.NewValue є недійсним, оскільки я хотів, щоб моїм замовчуванням був порожній рядок.
Делоріан

2
@Delorian Я оновив свою відповідь, дякую за ваші коментарі
Євгеній Мартиненко

1
Я змінив вашу публікацію, щоб підтримувати випадки, коли відмова відпадає більше ніж одне обмеження. Ваш сценарій видасть помилку, вказуючи, що @con вже оголошено. Я створив приватну змінну, щоб утримувати лічильник і просто збільшувати його. Я також змінив формат обмеження падіння, щоб більш точно відповідати тому, що EF надсилає SQL при створенні обмеження. Чудова робота над цим!
Бред

1
Дякую за рішення, але у мене є дві проблеми: 1. Назви таблиць потребують дужок. 2. В оновленні не встановлено нове значення, а встановлено значення за замовчуванням!
Омід-RH

1
Чи потрібно мені встановити [DatabaseGenerated(DatabaseGeneratedOption.Computed)]атрибут? Якщо так, то чому? У моїх тестах, здавалося, це не дало жодного ефекту, не випускаючи його з ладу.
RamNow

26

Властивості вашої моделі не повинні бути "автоматичними властивостями", хоча це і простіше. А атрибут DefaultValue - це лише інформативні метадані. Відповідь, прийнята тут, є однією альтернативою конструкторського підходу.

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

Це буде працювати лише для програм, що створюють та споживають дані за допомогою цього конкретного класу. Зазвичай це не проблема, якщо код доступу до даних централізований. Для оновлення значення для всіх програм вам потрібно налаштувати джерело даних, щоб встановити значення за замовчуванням. Відповідь Деві показує, як це можна зробити за допомогою міграцій, sql або будь-якої мови, якою розмовляє ваше джерело даних.


21
Примітка. Це не встановить значення за замовчуванням у базі даних . Інші програми, які не використовують вашу організацію, не отримають цього значення за замовчуванням.
Ерік Дж.

Ця частина відповіді, але вона не працює, якщо ви вставляєте записи іншими способами, ніж через Entity Framework. Крім того, якщо ви створюєте новий ненульовий стовпець у таблиці, це не дасть вам можливості встановити значення за замовчуванням для існуючих записів. @devi має цінне доповнення нижче.
d512

Чому ваш перший підхід "правильним" шляхом? Чи будете ви стикатися з ненавмисними проблемами з конструкторським підходом?
WiteCastle

питання думки, і ті змінюються :) І, мабуть, ні.
каленбойд

2
За певних обставин у мене виникли проблеми з конструкторським підходом; встановлення значення за замовчуванням у полі резервного копіювання здається найменш інвазивним рішенням.
Lucent Fox

11

Що я зробив, я ініціалізував значення в конструкторі сутності

Примітка: Атрибути DefaultValue не встановлюватимуть значення ваших властивостей автоматично, це потрібно зробити самостійно


4
Проблема конструктора, що встановлює значення, полягає в тому, що EF здійснюватиме оновлення бази даних під час здійснення транзакції.
Лука

Модель спочатку створила значення за замовчуванням таким чином
Лукас

DefaultValue - це вид, який живе в далеких, чужих краях, занадто сором'язливий, щоб сам стикатися з вашими властивостями. Не видавайте звуку, це легко лякається - якщо він коли-небудь наблизиться достатньо, щоб почути його. +1 за твердження, що це зовсім не очевидно.
Risadinha

8

Після коментаря @SedatKapanoglu я додаю весь свій підхід, який працює, тому що він мав рацію, просто використовувати вільний API не працює.

1- Створіть спеціальний генератор коду та замініть Створити для 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- Призначте новий генератор коду:

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

3- Використовуйте вільні api для створення Анотації:

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

Будь ласка, побачите моє повне рішення, я додав всю свою реалізацію того, як це працює, дякую.
Denny Puig

5

Це просто! Просто коментуйте із необхідним.

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

результуюча міграція буде:

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

Якщо ви хочете встановити істину, перед оновленням бази даних змініть параметр defaultValue на true на міграцію


автогенеровану міграцію потім можна змінити, і ви попрощаєтеся про значення за замовчуванням
Фернандо Торрес

Простий і працює, допомагає швидко додати стовпчик до існуючої таблиці з обмеженням зовнішнього ключа на новий стовпчик. Спасибі
po10cySA

А що, якщо нам потрібне значення за замовчуванням true?
Крістос Літрас

5

Я визнаю, що мій підхід уникає всієї концепції "Код Перший". Але якщо у вас є можливість просто змінити значення за замовчуванням у самій таблиці ... це набагато простіше, ніж довжини, які вам доведеться пройти вище ... Мені просто лінь робити всю цю роботу!

Майже здається, ніби оригінальна ідея плакатів спрацювала б:

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

Я думав, що вони просто помилилися, додавши цитати ... але на жаль, такої інтуїтивності немає. Інші пропозиції були для мене просто занадто великими (якщо я маю привілеї, необхідні, щоб зайти в таблицю і внести зміни ... де не кожен розробник буде в кожній ситуації). Врешті-решт я просто зробив це старомодно. Я встановлюю значення за замовчуванням у таблиці SQL Server ... Я маю на увазі дійсно, вже досить! ПРИМІТКА. Далі я перевірив, чи робив додаток-міграцію та оновлення бази даних та зміни застрягли. введіть тут опис зображення


1

Просто перевантажте конструктор за замовчуванням класу Model і передайте будь-який відповідний параметр, який ви можете або не можете використовувати. Цим ви можете легко поставити значення за замовчуванням для атрибутів. Нижче наведено приклад.

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

}

Ця пропозиція є повністю логічною стороною клієнта. Це "працює" до тих пір, поки ви будете взаємодіяти з базою даних за допомогою програми. Як тільки хтось хоче вставити запис вручну або з іншої програми, ви зрозумієте, що в схемі немає вираження за замовчуванням, і це не може масштабувати інших клієнтів. Хоча це не було чітко зазначено, ця законна тема передбачає вимогу отримати EF для створення міграції, яка розміщує вираз за замовчуванням у визначенні стовпця.
Том

0

Розглянемо, що у вас є назва класу з назвою Products і у вас є поле IsActive. просто вам потрібно створити конструктор:

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

Тоді ваше значення за замовчуванням IsActive - True!

Edite :

якщо ви хочете зробити це за допомогою SQL, використовуйте цю команду:

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

Просто це не було питанням. Йшлося про обмеження значень за замовчуванням у самій базі даних.
Габор

4
@Hatef, ваша редакція стосується лише EF Core. Питання щодо EF 6.
Майкл

3
Цей HasDefaultValueSql недоступний у EF6
татиго

0

У ядрі EF, випущеному 27 червня 2016 року, ви можете використовувати API для вільного налаштування для встановлення значення за замовчуванням. Перейдіть до класу ApplicationDbContext, знайдіть / створіть ім'я методу OnModelCreating та додайте наступний вільний API.

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

0

У .NET Core 3.1 ви можете виконати наступне в класі моделей:

    public bool? Active { get; set; } 

У DbContext OnModelCreating ви додаєте значення за замовчуванням.

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

        base.OnModelCreating(modelBuilder);
    }

Результатом цього є базу даних

введіть тут опис зображення

Примітка: Якщо у вас немає властивості nullable (bool?), Ви отримаєте таке попередження

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

Я виявив, що для використання завдання достатньо лише використання ініціалізатора автоматичних властивостей власності суб'єкта.

Наприклад:

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

2
Ви можете подумати, що це спрацює, спочатку з кодом. Але це не для мене з EF 6.2.
користувач1040323

-4

Хм ... Я роблю БД спочатку, і в цьому випадку це насправді набагато простіше. EF6 так? Просто відкрийте свою модель, клацніть правою кнопкою миші на стовпчику, для якого потрібно встановити за замовчуванням, виберіть властивості, і ви побачите поле "DefaultValue". Просто заповніть це і збережіть. Він встановить код для вас.

Ваш пробіг може залежати спочатку від коду, але я з цим не працював.

Проблема з багатьма іншими рішеннями полягає в тому, що, хоча вони можуть працювати спочатку, як тільки ви відновите модель, вона викине будь-який спеціальний код, який ви вставили у файл, створений машиною.

Цей метод працює, додаючи у файл edmx додаткову властивість:

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

І додавши необхідний код до конструктора:

public Thingy()
{
  this.Iteration = 1;

-5

Встановіть значення за замовчуванням для стовпця в таблиці на MSSQL Server та в код класу додайте атрибут, наприклад:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

за те саме майно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.