ChatGPT解决这个技术问题 Extra ChatGPT

Entity Framework 6 Code first 默认值

是否有“优雅”的方式为特定属性赋予默认值?

也许通过 DataAnnotations,类似于:

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

谢谢你。

也许在构造函数 this.Active = true; 中尝试?我认为在获取时数据库值将优先,但如果新建然后附加实体以进行更新而不首先获取,请小心,因为更改跟踪可能在您想要更新值时看到这一点.评论因为我很久没用EF了,感觉这是在摸黑。
感谢您的回复,到目前为止我一直使用这种方法stackoverflow.com/a/5032578/2913441,但我认为也许有更好的方法。
public bool Inactive { get; set; }😉
正如微软文档所说“您不能使用数据注释设置默认值”。

g
gdbdable

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

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

如果 OP 在创建 Event 对象时也没有专门将 Active 设置为 true,我不确定这是否有效。对于不可为空的 bool 属性,默认值始终为 false,因此除非更改,否则这就是实体框架将保存到数据库的内容。还是我错过了什么?
@GFoley83,是的,你是对的。此方法仅在数据库级别添加默认约束。对于完整的解决方案,您还需要在实体的构造函数中分配默认值或使用带有支持字段的属性,如上面的答案所示
这适用于基本类型。对于 DATETIMEOFFSET 之类的内容,请使用 defaultValueSql: "SYSDATETIMEOFFSET",而不是 defaultValue,因为此 defaultValue: System.DateTimeOffset.Now 将解析为当前系统 datetimeoffset 值的字符串。
AFAIK 如果重新构建迁移,您的手动更改将丢失
@ninbit 我认为您应该先编写迁移以在数据库级别将其删除,然后更改您的 DAL 映射
r
ravinsp

已经有一段时间了,但给别人留了个便条。我通过属性实现了所需的功能,并根据需要使用该属性装饰了模型类字段。

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

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

CodePlex 上的 EF

安迪·梅哈利克博客

我做了什么:

定义属性

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

然后在 Migration Configuration 构造函数中,注册自定义 SQL 生成器。

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

您甚至可以在全局范围内执行此操作,而无需将 [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()"));
请问自定义的SqlGenerator 在哪里?
自定义 SqlGenerator 来自这里:andy.mehalick.com/2014/02/06/…
@ravinsp 为什么不自定义 MigrationCodeGenerator 类以使用正确的信息而不是 SqlGenerator 代码进行迁移? SQL代码是最后一步......
@Alex 值得一试!这也可以工作,并且比注入 SQL 代码更优雅。但我不确定重写 C# MigrationCodeGenerator 的复杂性。
R
Rudey

上述答案确实有帮助,但只提供了部分解决方案。主要问题是,一旦您删除 Default value 属性,数据库中列的约束将不会被删除。所以以前的默认值仍将保留在数据库中。

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

用法

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

配置.cs 文件

通过注册自定义 Sql 生成器来更新您的 Configuration 类构造函数,如下所示:

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;

    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)
    {
        if (column.Annotations.TryGetValue("SqlDefaultValue", out var 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 tableNameSplitByDot = tableName.Split('.');
        var tableSchema = tableNameSplitByDot[0];
        var tablePureName = tableNameSplitByDot[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++;
        return str;
    }
}

这种方法对我来说非常有效。我所做的一项改进是还使用相同的逻辑覆盖 Generate(CreateTableOperation createTableOperation) 和 Generate(AddColumnOperation addColumnOperation),这样这些场景也会被捕获。我也只检查 values.NewValue 为空,因为我希望我的默认值为空字符串。
@Delorian 我已经更新了我的答案,谢谢你的评论
我已对您的帖子进行了编辑,以支持回滚删除多个约束的实例。您的脚本会抛出一个错误,指出 @con 已被声明。我创建了一个私有变量来保存一个计数器并简单地递增它。我还更改了删除约束的格式,以更接近地匹配 EF 在创建约束时发送给 SQL 的内容。在这方面做得很好!
感谢您的解决方案,但我有两个问题: 1. 表名需要括号。 2.在更新中没有设置新值,而是设置了默认值!
我需要设置 [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 属性吗?如果是这样,为什么?在我的测试中,将其排除在外似乎没有任何效果。
C
Community

您的模型属性不必是“自动属性”,即使这更容易。 DefaultValue 属性实际上只是提供信息的元数据。接受的答案 here 是构造函数方法的一种替代方法。

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's answer 展示了如何使用迁移、sql 或您的数据源使用的任何语言来完成。


注意:这不会在数据库中设置默认值。其他不使用您的实体的程序将不会获得该默认值。
这部分答案,但如果您以实体框架以外的方式插入记录,则它不起作用。此外,如果您在表上创建一个新的非空列,这将无法让您为现有记录设置默认值。 @devi 在下面有一个有价值的补充。
为什么您的第一种方法是“正确”的方法?您会在构造函数方法中遇到意想不到的问题吗?
见仁见智,这些变化:) 可能不会。
在某些情况下,我遇到了构造方法的问题;在支持字段中设置默认值似乎是侵入性最小的解决方案。
S
Sameh Deabes

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

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


构造函数设置值的问题是 EF 在提交事务时会更新数据库。
模型首先通过这种方式创建默认值
DefaultValue 是一种生活在遥远的外国土地上的物种,因为太害羞而无法独自遇到您的财产。不要发出声音,它很容易被吓跑——如果它靠近到足以听到它的话。 +1 表示完全不明显。
A
Anthony Griggs

我承认我的方法逃避了整个“代码优先”的概念。但是,如果您能够仅更改表本身的默认值...它比您必须通过上面的长度要简单得多...我只是懒得做所有这些工作!

似乎海报的原始想法似乎可行:

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

https://i.stack.imgur.com/GGJ0g.png


D
Denny Puig

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

请查看我的完整解决方案,我添加了它的所有实现方式,谢谢。
R
Roar Jørstad

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

导致数据库中出现以下内容

https://i.stack.imgur.com/rctQH.png

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

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

OP 的问题是关于 EF6,而不是 EF Core。
M
Marisol Gutiérrez

这很简单!只需用 required 注释即可。

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

由此产生的迁移将是:

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

如果要true,请在更新数据库之前在迁移中将defaultValue更改为true


然后可以更改自动生成的迁移,您将忘记默认值
简单且有效,有助于快速将列添加到现有表中,并在新列上具有外键约束。谢谢
如果我们需要默认值为 true 怎么办?
V
Velyo

我发现仅在实体属性上使用 Auto-Property Initializer 就足以完成工作。

例如:

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

你会认为这会起作用,首先是代码。但 EF 6.2 不适合我。
这实际上非常适用于 Code First EF 6。我不知道为什么这个答案的得分为负。我投了一票。
不适用于 EF Core 5.0.13。我最终只是手动编辑迁移以具有 ..., defaultValue: false);
R
Ruli
using System.ComponentModel;

[DefaultValue(true)]

public bool Active { get; set; }

正如目前所写的那样,您的答案尚不清楚。请edit添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。您可以找到有关如何写出好答案的更多信息in the help center
S
Sracanis

在 2016 年 6 月 27 日发布的 EF 核心中,您可以使用 fluent API 设置默认值。转到 ApplicationDbContext 类,找到/创建方法名称 OnModelCreating 并添加以下流式 API。

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

OP 询问 EF6,而不是 EF Core
如果有人感兴趣。我找到了一种在 EF Core 中使用 [DefaultValue] 属性(在此处的另一个答案中描述)的方法。 stackoverflow.com/a/64803061/1462234
B
Bappa Malakar

只需重载 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 创建一个将默认表达式放入列定义的迁移。
N
Nader Gharibian Fard

Entity Framework Core Fluent API HasDefaultValue 方法用于指定映射到属性的数据库列的默认值。该值必须是一个常数。

public class Contact
{
    public int ContactId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public bool IsActive { get; set; }
    public DateTime DateCreated { get; set; }
}
public clas SampleContext : DbContext
{
    public DbSet<Contact> Contacts { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Context>()
            .Propery(p => p.IsActive)
            .HasDefaultValue(true);
    }
}

或者

喜欢它!

您还可以指定用于计算默认值的 SQL 片段:

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

重复 this answer
H
Hatef.

假设您有一个名为 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!

编辑:

如果您想使用 SQL 执行此操作,请使用以下命令:

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

根本不是这个问题。这是关于数据库本身的默认值约束。
@Hatef,您的编辑仅适用于 EF Core。问题是关于 EF 6。
此 HasDefaultValueSql 在 EF6 中不可用
A
Acorndog

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

不过,您的里程可能首先因代码而异,但我没有使用过。

许多其他解决方案的问题在于,虽然它们最初可能有效,但一旦您重建模型,它就会丢弃您插入到机器生成的文件中的任何自定义代码。

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

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

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

public Thingy()
{
  this.Iteration = 1;

F
Fabien

在 MSSQL Server 中为表中的列设置默认值,并在类代码中添加属性,如下所示:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

对于相同的财产。