实体框架6代码优先默认值


203

有没有“优雅”的方法来赋予特定属性默认值?

也许是通过DataAnnotations,类似:

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

谢谢。


也许尝试在构造函数中this.Active = true;?我认为DB值在获取时将优先,但是要小心,如果new'ing然后附加一个实体进行更新而不先获取,因为更改跟踪可能会在您希望更新值时看到它。发表评论,因为我已经很长时间没有使用EF了,我觉得这是在黑暗中拍摄。
AaronLS

3
谢谢您的答复,到目前为止,我已经使用了此方法stackoverflow.com/a/5032578/2913441,但我认为也许有更好的方法。
marino-krk

2
public bool Inactive { get; set; }😉
杰西Hufstetler

正如Microsoft文档所说的“您不能使用数据注释设置默认值”。
hmfarimani

参阅

Answers:


166

您可以通过手动编辑代码优先迁移来做到这一点:

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

6
我不确定如果在创建对象时也未将OP专门设置Active为OP,那么这是否会起作用。默认值始终位于不可为null的bool属性上,因此,除非更改,否则这是实体框架将保存到数据库的内容。还是我错过了什么?trueEventfalse
GFoley83

6
@ GFoley83,是的,你是对的。此方法仅在数据库级别添加默认约束。为获得完整的解决方案,您还需要在实体的构造函数中分配默认值,或使用具有后备字段的属性,如上面的答案所示
gdbdable 2015年

1
这适用于基本类型。对于DATETIMEOFFSET之类的东西,请使用defaultValueSql:“ SYSDATETIMEOFFSET”,而不要使用defaultValue作为此defaultValue:System.DateTimeOffset.Now,它将解析为当前系统datetimeoffset值的字符串。
OzBob

3
AFAIK如果重新
安置

1
@ninbit我认为您应该先编写迁移操作以在数据库级别将其删除,然后再更改DAL映射
gdbdable

74

已经有一段时间了,但是给其他人留下了笔记。我通过属性实现了所需的功能,并根据需要用该属性装饰了模型类字段。

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

得到了这两篇文章的帮助:

我做了什么:

定义属性

[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()"));
DanielSkowroński,2016年

1
自定义SqlGenerator在哪里?
ᴍᴀᴛᴛʙᴀᴋᴇʀ

3
自定义SqlGenerator来自这里:andy.mehalick.com/2014/02/06/…–
ravinsp

1
@ravinsp为什么不自定义MigrationCodeGenerator类以使用正确的信息而不是SqlGenerator代码进行迁移?SQL代码是最后一步...
亚历

@亚历克斯值得尝试!这也将有效,并且比注入SQL代码更为优雅。但是我不确定重写C#MigrationCodeGenerator的复杂性。
ravinsp

73

以上答案确实有帮助,但仅提供了部分解决方案。主要问题是,一旦删除“默认值”属性,就不会删除对数据库中列的约束。因此,以前的默认值仍将保留在数据库中。

这是该问题的完整解决方案,包括删除属性删除方面的SQL约束。我还重用了.NET Framework的本机DefaultValue属性。

用法

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

为此,您需要更新IdentityModels.csConfiguration.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),因此也捕获了这些情况。我也只检查values.NewValue为null,因为我希望默认值为空字符串。
Delorian '16

2
@Delorian我已经更新了我的答案,谢谢您的评论
Jevgenij Martynenko

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

这仅适用于使用此特定类创建和使用数据的应用程序。如果数据访问代码是集中式的,通常这不是问题。要在所有应用程序中更新值,您需要配置数据源以设置默认值。Devi的答案显示了如何使用迁移,sql或数据源使用的任何语言来完成此任务。


21
注意:这不会在数据库中设置默认值。其他不使用您的实体的程序将不会获得该默认值。
Eric J.

答案的这一部分,但是如果您以非实体框架的方式插入记录,则此方法无效。同样,如果您要在表上创建新的非null列,这将使您无法为现有记录设置默认值。@devi在下面有一个有价值的补充。
d512

为什么您的第一种方法是“正确”的方法?构造函数方法会遇到意想不到的问题吗?
WiteCastle

意见问题,那些改变:)可能没有。
calebboyd 2015年

2
在某些情况下,我在构造方法上遇到了问题。在备用字段中设置默认值似乎是侵入性最小的解决方案。
朗讯·福克斯

11

我做了什么,我在实体的构造函数中初始化了值

注意:DefaultValue属性不会自动设置属性的值,您必须自己做


4
构造函数设置该值的问题在于,在提交事务时,EF将对数据库进行更新。
2015年

模型首先通过这种方式创建默认值
Lucas

DefaultValue是一个生活在遥远的异地的物种,它很害羞,无法独自遇到您的财产。不要发出声音,它很容易被吓跑-如果声音足够接近就可以听到。+1表示一点都不明显。
Risadinha

8

@SedatKapanoglu发表评论后,我添加了所有可行的方法,因为他是对的,仅使用流畅的API无效。

1-创建自定义代码生成器,并为ColumnModel覆盖Generate。

   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,然后再更新数据库


然后可以更改自动生成的迁移,并且您会忘记默认值
Fernando Torres

简单有效,有助于快速将一列添加到现有表,并在新列上使用外键约束。谢谢
po10cySA

如果我们需要默认值true怎么办?
Christos Lytras

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创建将默认表达式放入列定义的迁移的要求。
汤姆(Tom)

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!

编辑

如果要使用SQL进行此操作,请使用以下命令:

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

只是这不是问题。这是关于数据库本身的默认值约束。
加博尔

4
@Hatef,您的编辑仅适用于EF Core。问题是关于EF6。–
Michael

3
此HasDefaultValueSql在EF6中不可用
tatigo,

0

在2016年6月27日发布的EF core中,您可以使用fluent 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);
    }

在数据库中产生以下结果

在此处输入图片说明

注意:如果您的属性没有可为空的(布尔值?),则会收到以下警告

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而言并不适合我。
user1040323 '18 -10-11

-4

嗯...我先做数据库,在这种情况下,这实际上要容易得多。EF6对吗?只需打开模型,右键单击要为其设置默认值的列,选择属性,您将看到“ DefaultValue”字段。只需填写并保存。它将为您设置代码。

不过,您的里程可能会因代码而异,但我还没有处理过。

许多其他解决方案的问题在于,尽管它们最初可能会起作用,但是一旦您重建模型,它就会丢弃您插入到计算机生成的文件中的所有自定义代码。

该方法通过向edmx文件添加一个额外的属性来工作:

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

并通过向构造函数添加必要的代码:

public Thingy()
{
  this.Iteration = 1;

-5

设置MSSQL Server中表和类代码add属性中列的默认值,如下所示:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

对于相同的属性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.