Entity Framework 6 Code first 默认值
在使用Entity Framework 6 Code First时,如果要移除属性上的Default value特性,会导致数据库中的列约束不会被移除,从而导致之前的默认值仍然存在于数据库中。以下是解决该问题的完整方案,包括移除属性时的SQL约束。
首先,在类中使用以下特性和属性来定义默认值:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)] [DefaultValue("getutcdate()")] public DateTime CreatedOn { get; set; }
然后,在`IdentityModels.cs`文件中添加或更新`ApplicationDbContext`类中的方法:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); var convention = new AttributeToColumnAnnotationConvention("SqlDefaultValue", (p, attributes) => attributes.SingleOrDefault().Value.ToString()); modelBuilder.Conventions.Add(convention); }
接下来,在`Configuration.cs`文件中,更新`Configuration`类的构造函数,注册自定义的Sql生成器:
internal sealed class Configuration : DbMigrationsConfiguration{ public Configuration() { // DefaultValue Sql Generator SetSqlGenerator("System.Data.SqlClient", new DefaultValueSqlServerMigrationSqlGenerator()); } }
然后,添加自定义的Sql生成器类:
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(IEnumerablecolumns, 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 {dropConstraintCount} nvarchar(128) SELECT {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 {dropConstraintCount} IS NOT NULL EXECUTE('ALTER TABLE {tableSchema}.[{tablePureName}] DROP CONSTRAINT [' + {dropConstraintCount} + ']')"; dropConstraintCount++; return str; } }
这样就解决了在移除属性上的Default value特性时,数据库中的列约束仍然存在的问题。
Entity Framework 6 Code First 默认值问题的原因是,在使用Code First迁移时,如果不显式设置属性的默认值,Entity Framework会将非空布尔属性的默认值设置为false。这可能导致在数据库中保存不正确的默认值。解决这个问题的方法是在迁移代码中手动设置默认值,或者在实体的构造函数中设置默认值。
以下是解决方法的代码示例:
public override void Up() { AddColumn("dbo.Events", "Active", c => c.Boolean(nullable: false, defaultValue: true)); }
上述代码将在数据库层级添加默认约束。为了完整的解决方案,还需要在实体的构造函数中分配默认值,或者使用带有后备字段的属性。
对于基本类型,可以使用`defaultValueSql`而不是`defaultValue`来设置默认值。例如,对于`DATETIMEOFFSET`类型,可以使用`defaultValueSql: "SYSDATETIMEOFFSET"`。
对于希望了解如何添加迁移并在哪里放置代码的人,可以参考这个链接:[msdn.microsoft.com/en-us/library/jj591621(v=vs.113).aspx](https://msdn.microsoft.com/en-us/library/jj591621(v=vs.113).aspx),具体参考其中的“Data Motion / Custom SQL”部分。
需要注意的是,由于这个答案比较老,使用之前应该先尝试查看更近期的解决方案。如果有人对此答案有异议,请提出理由。
需要注意的是,如果重新生成迁移,手动更改将会丢失。
如果想要移除默认值,可以首先编写迁移代码以在数据库层级删除默认值,然后更改数据访问层的映射。
问题:Entity Framework 6 Code first中如何设置默认值?
原因:在Entity Framework 6 Code first中,默认情况下无法直接设置数据库表的默认值。然而,通过使用自定义的属性、自定义的SQL生成器以及自定义的迁移配置,可以实现设置默认值的功能。
解决方法:
1. 定义一个名为SqlDefaultValueAttribute的属性,用于标记需要设置默认值的属性。
2. 在DbContext的OnModelCreating方法中,使用modelBuilder.Conventions.Add方法将自定义的AttributeToColumnAnnotationConvention注册到EF中,以便在生成数据库表时能够读取属性上的SqlDefaultValueAttribute。
3. 自定义一个SQL生成器,并在其中编写逻辑,使之能够根据属性上的SqlDefaultValueAttribute来设置数据库表的默认值。
4. 在迁移配置中,将自定义的SQL生成器注册到EF中,以便在生成迁移脚本时能够使用自定义的SQL生成逻辑。
代码如下:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class SqlDefaultValueAttribute : Attribute { public string DefaultValue { get; set; } } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Add(new AttributeToColumnAnnotationConvention("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue)); } private void SetAnnotatedColumn(ColumnModel col) { AnnotationValues values; if (col.Annotations.TryGetValue("SqlDefaultValue", out values)) { col.DefaultValueSql = (string)values.NewValue; } } public class CustomMigrationSqlGenerator : SqlServerMigrationSqlGenerator { protected override void Generate(AddColumnOperation addColumnOperation) { base.Generate(addColumnOperation); SetAnnotatedColumn(addColumnOperation.Column); } } public Configuration() { SetSqlGenerator("System.Data.SqlClient", new CustomMigrationSqlGenerator()); }
另外,在使用Entity Framework Core时,可以通过以下代码实现相同的功能:
modelBuilder.Properties().Where(x => x.PropertyType == typeof(DateTime)).Configure(c => c.HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed).HasColumnAnnotation("SqlDefaultValue", "getdate()"));
需要注意的是,当使用EF进行插入操作时,模型对象属性中设置的值会覆盖数据库表中的默认值,因此设置数据库表的默认值在这种情况下是无效的。