Entity Framework 6 Code first Значение по умолчанию


204

Есть ли "элегантный" способ присвоить определенному свойству значение по умолчанию?

Может быть, 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; }Jes
Джесси Хуфштетлер

как говорят в документах Microsoft: «Вы не можете установить значение по умолчанию, используя аннотации данных».
Хмфаримани

Ответы:


167

Вы можете сделать это, вручную отредактировав код первой миграции:

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

6
Я не уверен , что это будет работать , если ОП не специально созданной Activeдля trueпри создании Eventобъекта , а также. Значение по умолчанию всегда falseуказывается в свойстве bool, не допускающем значения NULL, поэтому, если оно не изменено, это структура сущностей будет сохранять в БД. Или я что-то упустил?
GFoley83

6
@ GFoley83, да, ты прав. Этот метод добавляет ограничение по умолчанию только на уровне базы данных. Для полного решения вам также необходимо назначить значение по умолчанию в конструкторе объекта или использовать свойство с полем с резервной
копией,

1
Это работает для базовых типов. Для чего-то вроде DATETIMEOFFSET используйте, defaultValueSql: «SYSDATETIMEOFFSET», а НЕ defaultValue, так как значение defaultValue: System.DateTimeOffset.Now будет преобразовано в строку текущего значения datetimeoffset системы.
ОзБоб

3
AFAIK Ваши ручные изменения будут потеряны в случае повторного создания леса миграции
Александр Powolozki

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) ДобавитьmodelBuilder.Properties().Where(x => x.PropertyType == typeof(DateTime)).Configure(c => c.HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed).HasColumnAnnotation("SqlDefaultValue", "getdate()"));
Даниэль Сковронски

1
Где пользовательский SqlGenerator, пожалуйста?
ʙᴀᴋᴇʀ ʙᴀᴋᴇʀ

3
Пользовательский SqlGenerator пришел отсюда: andy.mehalick.com/2014/02/06/…
ravinsp

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

@ Алекс Стоит попробовать! Это также будет работать и будет более элегантным, чем внедрение кода 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 имеет значение null, так как я хотел, чтобы по умолчанию была пустая строка.
Делориан

2
@Delorian Я обновил свой ответ, спасибо за ваши комментарии
Евгений Мартыненко

1
Я внес изменения в ваш пост, чтобы поддержать случаи, когда откат отбрасывает более одного ограничения. Ваш скрипт выдаст ошибку, сообщающую, что @con уже объявлен. Я создал приватную переменную для хранения счетчика и просто увеличил его. Я также изменил формат ограничения удаления, чтобы более точно соответствовать тому, что EF отправляет в SQL при создании ограничения. Отличная работа над этим!
Брэд

1
Спасибо за решение, но у меня есть две проблемы: 1. Для имен таблиц нужны скобки. 2. В обновлении новое значение не установлено и вместо него установлено значение по умолчанию!
Omid-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; }
    }
}

против

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 будет выполнять обновление базы данных при фиксации транзакции.
Лука

Модель сначала создает значения по умолчанию этим способом
Lucas

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

8

После комментария @SedatKapanoglu я добавляю все свои подходы, которые работают, потому что он был прав, просто использование свободного API не работает.

1- Создайте собственный генератор кода и переопределите Generate для 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);            
}

Пожалуйста, можете увидеть мое полное решение, я добавил все мои реализации, как это работает, спасибо.
Денни Пуиг

5

Это просто! Просто комментируйте с необходимыми.

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

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

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

Если вы хотите true, измените defaultValue на true в миграции перед обновлением базы данных


Затем можно изменить автоматически созданную миграцию, и вы забудете о значении по умолчанию
Фернандо Торрес,

Просто и работает, помогает быстро добавить столбец в существующую таблицу с ограничением внешнего ключа для нового столбца. Спасибо
po10cySA

А что если нам нужно значение по умолчанию true?
Христос Литрас

5

Я признаю, что мой подход избегает всей концепции «Code First». Но если у вас есть возможность просто изменить значение по умолчанию в самой таблице ... это намного проще, чем длины, которые вы должны пройти выше ... Мне просто лень делать всю эту работу!

Кажется, что оригинальная идея постеров сработает:

[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. просто вам нужен конструктор create:

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
tatigo

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

Я обнаружил, что достаточно просто использовать Auto-Property Initializer для свойства объекта, чтобы выполнить работу.

Например:

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

2
Вы могли бы подумать, что это будет работать, с кодом в первую очередь. Но это не для меня с EF 6.2.
user1040323

-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.