实体框架的 SQL 列默认值

Posted

技术标签:

【中文标题】实体框架的 SQL 列默认值【英文标题】:SQL column default value with Entity Framework 【发布时间】:2015-01-18 06:01:20 【问题描述】:

我正在尝试将 Code-First EF6 与默认 SQL 值一起使用。

例如,我有一个“CreatedDate”列/属性不为空,SQL 中的默认值为“getdate()”

如何在我的代码模型中表示这一点?目前我有:

<DatabaseGenerated(DatabaseGeneratedOption.Computed)>
Public Property CreatedDate As DateTime

这是否可行,或者即使实际列不应为空,我是否需要使用可为空,因此 EF 在尚未设置时不会发送值:

<DatabaseGenerated(DatabaseGeneratedOption.Computed)>
Public Property CreatedDate As DateTime?

或者有更好的解决方案吗?

我不希望 EF 处理我的默认值 - 我知道这对我来说是可用的,但在我目前的情况下是不可能的。

【问题讨论】:

请参考https://***.com/a/59551802/8403632 感谢@shalithasenanayaka,但您提到的答案是由应用程序中的逻辑计算的,我最初的问题(虽然回答很长)是关于允许 SQL 提供默认值。您链接到的答案不是解决此问题的可行方法。还是谢谢。 【参考方案1】:

目前在 EF6 中没有一个属性来定义用于某个属性默认值的数据库函数。您可以对 Codeplex 进行投票以实现它:

https://entityframework.codeplex.com/workitem/44

实现类似功能的公认方法是将Computed 属性与Migrations 一起使用,您可以在其中指定默认数据库函数。

您的类在 C# 中可能如下所示:

public class MyEntity

    [Key]
    public int Id  get; set; 
    public string Name  get; set; 

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime Created  get; set; 

计算的属性不必是可空的。

然后您必须运行迁移并手动修改它以包含默认 SQL 函数。迁移可能如下所示:

public partial class Initial : DbMigration

    public override void Up()
    
        CreateTable(
            "dbo.MyEntities",
            c => new
                
                    Id = c.Int(nullable: false, identity: true),
                    Name = c.String(),
                    Created = c.DateTime(nullable: false, defaultValueSql: "GetDate()"),
                )
            .PrimaryKey(t => t.Id);

    

    public override void Down()
    
        DropTable("dbo.MyEntities");
    

您会注意到 defaultValueSql 函数。这是让计算工作的关键

【讨论】:

【参考方案2】:

接受的答案对于 EF6 是正确的,我只添加 EF Core 解决方案; (我的解决方案也侧重于更改默认值,而不是第一次正确创建它

EF Core 中仍然没有 Data-Attribute。

而且您仍然必须使用 Fluent API;它确实有一个HasDefaultValue

protected override void OnModelCreating(ModelBuilder modelBuilder)

    modelBuilder.Entity<Blog>()
        .Property(b => b.Rating)
        .HasDefaultValue(3);

注意,NULL 情况下还有HasDefaultValueSql

        .HasDefaultValueSql("NULL");

您还可以使用迁移UpDown 方法,您可以更改defaultValuedefaultValueSql,但您可能需要先删除索引。这是一个例子:

public partial class RemovingDefaultToZeroPlantIdManualChange : Migration

    protected override void Up(MigrationBuilder migrationBuilder)
    
        migrationBuilder.DropIndex(
            name: "IX_TABLE_NAME_COLUMN_NAME",
            table: "TABLE_NAME"
        );

        migrationBuilder.AlterColumn<int>(
            name: "COLUMN_NAME",
            table: "TABLE_NAME",
            nullable: true,
            //note here, in the Up method, I'm specifying a new defaultValue:
            defaultValueSql: "NULL",
            oldClrType: typeof(int));

        migrationBuilder.CreateIndex(
            name: "IX_TABLE_NAME_COLUMN_NAME",
            table: "TABLE_NAME",
            column: "COLUMN_NAME"
        );
    

    protected override void Down(MigrationBuilder migrationBuilder)
    
        migrationBuilder.DropIndex(
            name: "IX_TABLE_NAME_COLUMN_NAME",
            table: "TABLE_NAME"
        );

        migrationBuilder.AlterColumn<int>(
            name: "COLUMN_NAME",
            table: "TABLE_NAME",
            nullable: true,
            //note here, in the Down method, I'll restore to the old defaultValue:
            defaultValueSql: "0",
            oldClrType: typeof(int));

        migrationBuilder.CreateIndex(
            name: "IX_TABLE_NAME_COLUMN_NAME",
            table: "TABLE_NAME",
            column: "COLUMN_NAME"
        );


    

【讨论】:

【参考方案3】:

[mysql]

对于那些不想在每次数据库更新后使用计算并重写它的人,我为数据库部分类编写了扩展方法。当然,有些东西需要改进或添加,但现在已经足够我们使用了,享受吧。

考虑到,由于 database_schema 访问它不是最快的,而且您需要具有与表名相同的实体名称(或以某种方式重写)。

    public static bool GetDBDefaults(object entity)
    
        try
        
            string table_name = entity.GetType().Name;

            string q = $"select column_name, column_default from information_schema.columns where column_default is not null and table_schema not in ('information_schema', 'sys', 'performance_schema', 'mysql') and table_name = 'table_name' order by table_schema, table_name, ordinal_position;";

            List<DBDefaults> dbDefaults = new List<DBDefaults>();
            using (DatabaseModelFull db = new DatabaseModelFull())
            
                dbDefaults = db.Database.SqlQuery<DBDefaults>(q).ToList();
            

            Type myType = entity.GetType();
            IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
            IList<FieldInfo> fields = new List<FieldInfo>(myType.GetFields());

            foreach (var dbDefault in dbDefaults)
            
                var prop = props.SingleOrDefault(x => x.Name == dbDefault.column_name);

                if (prop != null)
                
                    if (dbDefault.column_default.Equals("CURRENT_TIMESTAMP"))
                        prop.SetValue(entity, System.Convert.ChangeType(DateTime.Now, prop.PropertyType));
                    else
                        prop.SetValue(entity, System.Convert.ChangeType(dbDefault.column_default, prop.PropertyType));
                    continue;
                

                var field = fields.SingleOrDefault(x => x.Name == dbDefault.column_name);

                if (field != null)
                
                    if (dbDefault.column_default.Equals("CURRENT_TIMESTAMP"))
                        field.SetValue(entity, System.Convert.ChangeType(DateTime.Now, field.FieldType));
                    else
                        field.SetValue(entity, System.Convert.ChangeType(dbDefault.column_default, field.FieldType));
                
            
            return true;
        
        catch
        
            return false;
        
    


    public class DBDefaults
    
        public string column_name  get; set; 
        public string column_default  get; set; 
    

【讨论】:

【参考方案4】:

在遇到同样问题后添加此解决方案。我想使用 SQL Server 中定义的默认值,而不必在 C# 代码中设置默认值。

此行为由 DatabaseGeneratedOption 值控制。 EF 使用默认值、NULL 值还是指定值取决于使用的选项。以下是为每个选项创建新数据库条目以及是否使用默认值的简化示例。

// Item database declaration
public Item()

    public string id  get; set; 
    public string description  get; set 


// Add item to database code.  First one uses the default value, the second is
// overriding it using the specified value.  Anything inside brackets uses your
// database context and class definition objects.
var itemToAdd1 = new [dbClass].Item

    id = "CodeTest1"
;
var itemToAdd2 = new new[DBClass].Item

    id = "CodeTest2",
    description = "Default value override in code"
;
[dbContext].Add(itemToAdd1);
[dbContext].Add(itemToAdd2);
[dbContext].SaveChanges();


// The behavior changes based on the DatabaseGeneratedOption setting on the database
// class definition for the description property.

// DatabaseGeneratedOption:  None
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string description  get; set; 

Result:
id = "CodeTest1",                                   // First item, using default value
description = NULL                                  // Code sent null value and SQL used it instead of the default

id = "CodeTest2",                                   // Second item, overriding description in code
description = "Default value override in code"      // Code override value was used by SQL as expected


// DatabaseGeneratedOption:  Identity
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string description  get; set; 

Result:
id = "CodeTest1",                                   // First item, using default value
description = "SQL Default Value"                   // Code did not send any value and SQL used the DB default value as expected

id = "CodeTest2",                                   // Second item, overriding description in code
description = "Default value override in code"      // Code override value was used by SQL as expected


// DatabaseGeneratedOption:  Computed
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string description  get; set; 

Result:
id = "CodeTest1",                                   // First item, using default value
description = "SQL Default Value"                   // Code did not send any value and SQL used the DB default value as expected

id = "CodeTest2",                                   // Second item, overriding description in code
description = "SQL Default Value"                   // The SQL default value was still used despite setting this property in code.

TLDR: DatabaseGeneratedOption.Identity 应该为您提供您正在寻找的结果。

【讨论】:

【参考方案5】:

这是我的解决方案。我在 .Net Core 3.1 DLL 的 EF Core DbContext 中使用了它。它适用于自动迁移,并使用一个属性,该属性通过反射进行搜索。该属性包含默认的 sql 值,然后在 OnModelCreating() 中设置。

第 1 步 - 添加新属性
[AttributeUsage(AttributeTargets.Property)]
public class DefaultValueSqlAttribute : Attribute

    public string DefaultValueSql  get; private set;  = "";
    
    public DefaultValueSqlAttribute(string defaultValueSql)
    
        DefaultValueSql = defaultValueSql;
    

第 2 步 - 在数据类中使用该属性
public class Entity

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("Id", Order=0)]
    [DefaultValueSql("newId()")]
    public Guid Id  get; set; 


    [Column("DateCreated", Order = 100)]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [DefaultValueSql("GETUTCDATE()")]
    public DateTime DateCreated  get; set;  = DateTime.UtcNow;


[Table("tbl_User")]
public class User: Entity

    [Required]
    [Column(Order = 1)]
    public string EMail  get; set; 

    [Column(Order = 2)]
    public string Name  get; set; 

    [Column(Order = 3)]
    public string Forename  get; set; 

    [Column(Order = 4)]
    public string Street  get; set; 

    [Column(Order = 5)]
    public string Postalcode  get; set; 

    [Column(Order = 6)]
    public string MobileNumber  get; set; 

第 3 步 - 将您的类添加到 DbContext
public DbSet<User> tbl_User  get; set; 
第 4 步 - 将此代码添加到您的 DbContext(您必须根据需要对其进行更改...) 它假定所有相关的数据类都存在于一个特定的程序集和一个特定的命名空间中。在我的例子中,它是一个 .NetStandard 2.0 DLL。
protected override void OnModelCreating(ModelBuilder mb)

    //Uncomment this line, if you want to see what is happening when you fire Add-Migration
    //Debugger.Launch();

    base.OnModelCreating(mb);

    OnModelCreatingAddDefaultSqlValues(mb);


private void OnModelCreatingAddDefaultSqlValues(ModelBuilder mb)

    var assemblyName = "Ik.Shared";
    var nameSpace = "Ik.Shared.Entities";

    var asm = Assembly.Load(assemblyName);

    //Read all types in the assembly Ik.Shared, that are in the namespace Ik.Shared.Entities
    List<Type> types = asm.GetTypes().Where(p => p.Namespace == nameSpace).ToList();
    //Read all properties in DatabaseContext, that are of type DbSet<>
    var dbSets = typeof(DatabaseContext).GetProperties().Where(p => p.PropertyType.Name.ToLower().Contains("dbset")).ToList();
    //A list of types, that are used as a generic argument in a DbSet<T>
    List<Type> dbSetTypes = new List<Type>();
    foreach (PropertyInfo pi in dbSets)
    
        //Add the type of the generic argument from DbSet<T>
        dbSetTypes.Add(pi.PropertyType.GetGenericArguments()[0]);
    

    //For all types in Ik.Shared
    foreach (Type t in types)
    
        //If a type inherited from Entity AND the type itself is not Entity AND the type was used as DbSet<Type> in the DbContext
        if (typeof(Entity).IsAssignableFrom(t) && t.Name != nameof(Entity) && dbSetTypes.Contains(t))
        
            //Get all properties of that type
            var properties = t.GetProperties().ToList();
            foreach (var p in properties)
            
                //Check if the property has the DefaultValueSqlAttribute
                var att = p.GetCustomAttribute<DefaultValueSqlAttribute>();
                if (att != null)
                
                    //If any property has the DefaultValueSqlAttribute, set the the value here. Done. 
                    mb.Entity(t).Property(p.Name).HasDefaultValueSql(att.DefaultValueSql);
                
            
        
    

第 5 步 - 在 Visual Studio 的打包程序控制台中启动新的迁移
Add-Migration MI_000000 -StartupProject Ik.Ws.Login
第 6 步 - 查看结果:查看 defaultValueSql = Done
//This class was entirely created automatic. No manual changes.
public partial class MI_000000 : Migration

    protected override void Up(MigrationBuilder migrationBuilder)
    
        migrationBuilder.CreateTable(
            name: "tbl_User",
            columns: table => new
            
                Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "newId()"),
                EMail = table.Column<string>(type: "nvarchar(max)", nullable: false),
                Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
                Forename = table.Column<string>(type: "nvarchar(max)", nullable: true),
                Street = table.Column<string>(type: "nvarchar(max)", nullable: true),
                Postalcode = table.Column<string>(type: "nvarchar(max)", nullable: true),
                MobileNumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
                DateCreated = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()")
            ,
            constraints: table =>
            
                table.PrimaryKey("PK_tbl_User", x => x.Id);
            );
    

    protected override void Down(MigrationBuilder migrationBuilder)
    
        migrationBuilder.DropTable(
            name: "tbl_User");
    

希望对你有所帮助或启发。

【讨论】:

【参考方案6】:

试试这个。此代码默认插入当前日期

//[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime Created  get; set;  = new DateTime();

【讨论】:

不,它没有。

以上是关于实体框架的 SQL 列默认值的主要内容,如果未能解决你的问题,请参考以下文章

如何在实体框架中设置默认值

禁用实体框架的默认值生成(代码优先)

实体框架和 SQL Server 表属性

sql语句 给表增加一列并设置默认值

实体框架不包括插入查询中具有默认值的列

实体框架支持来自数据库优先方法的默认约束