今天小伙伴在代码中遇到了有关 HasDefaultValue 的疑问,这里整理澄清下...
在使用 Entity Framework Core (EFCore) 配置实体时,HasDefaultValue
方法会为数据库列设置一个默认值。该默认值的行为取决于以下条件:
1. 配置 HasDefaultValue
的应用场景
HasDefaultValue
主要在以下场景中生效:
- 首次创建表时:当迁移生成
CREATE TABLE
的 SQL 脚本时,会在列定义中附带默认值。 - 列被新增到现有表时:在迁移中,新增列时,如果指定了
HasDefaultValue
,生成的 SQL 脚本会为新增列设置默认值。
示例:
modelBuilder.Entity<MyEntity>() .Property(e => e.MyColumn) .HasDefaultValue(100);
2. 默认值的应用条件
1) 创建表
如果使用迁移创建表,HasDefaultValue
会为列添加默认值。例如:
CREATE TABLE MyEntity ( Id INT PRIMARY KEY, MyColumn INT DEFAULT 100
);
2) 添加新列
如果表已经存在,且通过迁移为现有表新增列,HasDefaultValue
会生成如下 SQL:
ALTER TABLE MyEntity ADD MyColumn INT DEFAULT 100;
在添加列时,DEFAULT
值会应用到已存在的行。
3) 插入数据
当应用程序通过 EFCore 插入记录,而未显式为列提供值时:
- EFCore 会让数据库使用定义的默认值(依赖于数据库执行)。
注意:EFCore 不会在内存中自动填充默认值到实体属性上。
3. 特殊条件下的行为
1) 与 HasDefaultValueSql
的区别
HasDefaultValue
:直接定义一个常量值。HasDefaultValueSql
:允许使用 SQL 表达式设置默认值。
例如:
modelBuilder.Entity<MyEntity>() .Property(e => e.MyColumn) .HasDefaultValueSql("GETDATE()");
生成的 SQL:
ALTER TABLE MyEntity ADD MyColumn DATETIME DEFAULT GETDATE();
2) 更新表格时的影响
如果尝试将现有列的默认值修改为另一个值,EFCore 迁移中不会自动生成 ALTER COLUMN
SQL,需要手动调整迁移。
3) 在内存中的作用
在运行时,HasDefaultValue
不会对未赋值的属性生效,即在 EFCore 中 HasDefaultValue
仅影响数据库的默认行为。
4. 实际示例与注意事项
代码示例
public class MyEntity
{ public int Id { get; set; } public int MyColumn { get; set; }
} // 配置
modelBuilder.Entity<MyEntity>() .Property(e => e.MyColumn) .HasDefaultValue(100);
生成的迁移代码
migrationBuilder.CreateTable( name: "MyEntity",columns: table => new { Id = table.Column<int>(nullable: false) .Annotation("SqlServer:Identity", "1, 1"), MyColumn = table.Column<int>(nullable: false, defaultValue: 100) }, constraints: table => { table.PrimaryKey("PK_MyEntity", x => x.Id); });
插入数据的行为
- 如果你执行如下代码:
context.MyEntities.Add(new MyEntity { Id = 1 });
context.SaveChanges();
结果中 MyColumn
的值会由数据库设置为 100
。
5. 总结
HasDefaultValue
在以下情况下生效:
- 数据库表初次创建时。
- 为现有表新增列时。
- 插入数据时,如果 EFCore 没有为该列赋值,则数据库会自动使用默认值。
如果你想在代码中为未赋值的属性提供默认值,需额外在 C# 类中定义默认值逻辑。
在使用 Entity Framework Core 创建实体并保存到数据库时,如果你为属性赋值了,即使这个值与 HasDefaultValue
配置的值相同,EFCore 的行为取决于以下几个因素:
1. EFCore 的默认行为
- 显式赋值的属性: 即使属性的值与
HasDefaultValue
中定义的值相同,EFCore 会将其视为已显式设置。因此,生成的 SQL 插入语句会包含该字段及其值。
示例:
modelBuilder.Entity<MyEntity>() .Property(e => e.MyColumn) .HasDefaultValue(100); var entity = new MyEntity
{ MyColumn = 100 // 显式赋值与默认值相同
}; context.MyEntities.Add(entity);
context.SaveChanges();
生成的 SQL 类似如下:
INSERT INTO MyEntity (MyColumn) VALUES (100);
2. 如何忽略字段
EFCore 只有在属性未显式赋值(即为 null
或默认类型值,如 0
对于整型)时,才会让数据库使用 HasDefaultValue
配置的默认值。
示例:
var entity = new MyEntity(); // 未赋值
MyColumn context.MyEntities.Add(entity);
context.SaveChanges();
生成的 SQL 类似如下:
INSERT INTO MyEntity DEFAULT VALUES;
如果表的 MyColumn
定义了默认值 100
,那么数据库会将其设置为 100
。
3. 如何避免显式赋值时重复生成字段
如果你希望 EFCore 在插入数据时忽略与默认值相同的显式赋值字段,可以通过以下方式实现:
1) 使用默认值优化器
手动检测属性值是否等于默认值,并在这种情况下将其设置为 null
:
var entity = new MyEntity
{ MyColumn = 100 // 与默认值相同
}; if (entity.MyColumn == 100)
{ entity.MyColumn = default; // 设置为默认值
}context.MyEntities.Add(entity);
context.SaveChanges();
生成的 SQL:
INSERT INTO MyEntity DEFAULT VALUES;
2) 自定义 SaveChanges 拦截器
在 DbContext.SaveChanges
方法中拦截并移除与默认值相同的字段:
public override int SaveChanges()
{ foreach (var entry in ChangeTracker.Entries<MyEntity>()) { if (entry.State == EntityState.Added && entry.Entity.MyColumn == 100) {entry.Property(e => e.MyColumn).IsModified = false; } } return base.SaveChanges();
}
4. 总结
- 如果为属性显式赋值,即使值与
HasDefaultValue
一样,EFCore 会生成包含该字段的 SQL。 - 要避免生成重复字段,可以在代码逻辑中手动移除默认值的赋值,或使用自定义拦截器动态处理。
- 默认值 (
HasDefaultValue
) 仅在未赋值或未提供字段时,由数据库自动填充。
补充:
配置字段为数据库生成
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Example>() .Property(e => e.Age) .HasDefaultValueSql("18");
}
归纳一下:
-
HasDefaultValueSql("18")
的作用:- 它的主要作用是 在模型构建时告诉 EF Core 该字段在数据库中有默认值。
- EF Core 不会强制使用
HasDefaultValueSql
配置的值生成INSERT
语句,而是让数据库的默认值(DEFAULT
定义)生效。
-
EF Core 生成
INSERT
的行为:- 如果字段没有显式赋值(即在代码中未为该字段赋值),EF Core 不会在生成的
INSERT
语句中包含该字段,从而依赖数据库在SCHEMA
上的DEFAULT
值。 - 如果代码中显式赋值了该字段,即使值与
HasDefaultValueSql
的配置一致,EF Core 仍会在INSERT
语句中包含该字段,并使用显式赋值的值。
- 如果字段没有显式赋值(即在代码中未为该字段赋值),EF Core 不会在生成的
-
实际生效的值:
- 关键是数据库表定义中的
DEFAULT
值(由CREATE TABLE
或ALTER TABLE
设置)。 HasDefaultValueSql
中配置的 SQL 只是 帮助 EF Core 在迁移中设置数据库表的默认值,一旦表的SCHEMA
定义了默认值,EF Core 就不再干涉。
- 关键是数据库表定义中的
-
HasDefaultValueSql
的核心意义:- 它是在 数据库迁移时,帮助生成类似如下的 SQL:
ALTER TABLE Example ADD CONSTRAINT DF_Age DEFAULT 18 FOR Age;
- 一旦表的默认值设置完成,EF Core 的
HasDefaultValueSql
配置就不再直接影响运行时行为。
- 它是在 数据库迁移时,帮助生成类似如下的 SQL:
示例验证
表定义
CREATE TABLE Example ( Id INT PRIMARY KEY, Name NVARCHAR(50), Age INT DEFAULT 18 );
Fluent API 配置
protected override void OnModelCreating(ModelBuilder modelBuilder)
{modelBuilder.Entity<Example>() .Property(e => e.Age) .HasDefaultValueSql("18");
}
插入代码
var example = new Example
{ Name = "Alice" // Age 未赋值 }; context.Examples.Add(example);context.SaveChanges();
}
生成的 SQL:
INSERT INTO Example (Name) VALUES ('Alice'); -- Age 列未包含
数据库最终插入:
Id | Name | Age 1 | Alice | 18
如果数据库默认值改变
假如数据库表的 DEFAULT
定义改为 20
,代码和 Fluent API 不变:
生成的 SQL 仍然是:
INSERT INTO Example (Name) VALUES ('Alice'); -- Age 列未包含
但最终插入的值会变为:
Id | Name | Age 1 | Alice | 20
这说明 运行时实际生效的是数据库表的 DEFAULT
值。
总结
HasDefaultValueSql
的配置值不会直接影响运行时行为,其作用主要是用于迁移时生成表的默认值定义。- 最终起作用的是数据库表的默认值,而不是
HasDefaultValueSql
配置中的值。 - 只要字段未显式赋值,无论是可空还是非可空,EF Core 都会依赖数据库表的默认值生效。