数据库列可以采用各种方式生成其值:主键列经常自动递增整数,其他列具有默认值或计算值等。本页详细介绍了使用 EF Core 生成配置值的各种模式。
默认值
在关系数据库上,可以使用默认值配置列;如果插入的行没有该列的值,则使用默认值。
可以在属性上配置默认值:
modelBuilder.Entity<Blog>()
.Property(b => b.Rating)
.HasDefaultValue(3);
还可以指定用于计算默认值的 SQL 片段:
modelBuilder.Entity<Blog>()
.Property(b => b.Created)
.HasDefaultValueSql("getdate()");
从 EF 10 开始,对于 SQL Server,可以显式指定默认值约束的名称,从而更好地控制数据库架构。
modelBuilder.Entity<Blog>()
.Property(b => b.Rating)
.HasDefaultValue(3, "DF_Blog_IsActive");
modelBuilder.Entity<Blog>()
.Property(b => b.Created)
.HasDefaultValueSql("getdate()" , "DF_Blog_IsActive");
还可以调用 UseNamedDefaultConstraints
以启用所有默认约束的自动命名。 请注意,如果有现有迁移,则添加的下一个迁移将重命名模型中的每一个默认约束。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.UseNamedDefaultConstraints();
}
计算列
在大多数关系数据库中,可以将列配置为在数据库中计算其值,通常使用引用其他列的表达式:
modelBuilder.Entity<Person>()
.Property(p => p.DisplayName)
.HasComputedColumnSql("[LastName] + ', ' + [FirstName]");
上述过程创建了虚拟计算列,每次从数据库提取时都会计算其值。 还可以指定 存储 计算列(有时称为 持久化),这意味着该列在每次行更新时计算,并且与常规列一起存储在磁盘上。
modelBuilder.Entity<Person>()
.Property(p => p.NameLength)
.HasComputedColumnSql("LEN([LastName]) + LEN([FirstName])", stored: true);
主键
按照惯例,短、int、long 或 Guid 类型的非复合主键被设置为在应用程序未提供值时为插入实体自动生成值。 数据库提供程序通常负责必要的配置;例如,SQL Server 中的数字主键会自动设置为 IDENTITY 列。
有关详细信息,请参阅有关特定继承映射策略的密钥和指南的文档。
显式配置值生成
我们看到上面,EF Core 会自动为主键设置值生成,但我们可能希望为非键属性进行相同的设置。 可以将任何属性配置为为插入的实体生成其值,如下所示:
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime Inserted { get; set; }
}
同样,可以将属性配置为在添加或更新时生成其值:
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime LastUpdated { get; set; }
}
与默认值或计算列不同,我们不指定 如何 生成值;取决于所使用的数据库提供程序。 数据库提供程序可能会自动为某些属性类型设置值生成,但其他提供程序可能需要手动设置如何生成值。
例如,在 SQL Server 上,当 GUID 属性配置为主键时,提供程序会在客户端自动使用算法生成最佳顺序 GUID 值。 但是,在 ValueGeneratedOnAdd DateTime 属性上指定将不起作用(请参阅下面的部分了解 DateTime 值生成)。
在添加或更新时配置为生成,并标记为并发令牌的 byte[] 属性,会使用 rowversion 数据类型进行设置,以便值在数据库中自动生成。 但是,指定 ValueGeneratedOnAdd 不起作用。
有关它支持的特定值生成技术,请参阅提供商的文档。 可在 此处找到 SQL Server 值生成文档。
日期/时间值生成
常见的请求是具有一个数据库列,其中包含第一次插入行的时间(在添加时生成的值),或上次更新时(添加或更新时生成的值)。 由于有各种策略来实现这一点,EF Core 数据库提供程序通常不会自动为日期/时间列设置值生成 - 你需要自行配置这个过程。
创建时间戳
将日期/时间列配置为具有行的创建时间戳通常是使用适当的 SQL 函数配置默认值的问题。 例如,在 SQL Server 上,可以使用以下内容:
modelBuilder.Entity<Blog>()
.Property(b => b.Created)
.HasDefaultValueSql("getdate()");
请务必选择适当的函数,因为可能存在多个函数(例如 GETDATE()
与) GETUTCDATE()
。
更新时间戳
尽管存储的计算列似乎是管理上次更新时间戳的好解决方案,但数据库通常不允许在计算列中指定诸如 GETDATE()
等函数。 或者,可以设置数据库触发器来实现相同的效果:
CREATE TRIGGER [dbo].[Blogs_UPDATE] ON [dbo].[Blogs]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF ((SELECT TRIGGER_NESTLEVEL()) > 1) RETURN;
UPDATE B
SET LastUpdated = GETDATE()
FROM dbo.Blogs AS B
INNER JOIN INSERTED AS I
ON B.BlogId = I.BlogId
END
有关创建触发器的信息, 请参阅有关在迁移中使用原始 SQL 的文档。
重写值生成
尽管为生成值配置了属性,但在许多情况下,仍可能为其显式指定值。 这是否实际起作用取决于已配置的特定值生成机制;虽然可以指定显式值而不是使用列的默认值,但不能对计算列执行相同的作。
若要使用显式值替代值生成,只需将该属性设置为任何非该属性类型的 CLR 默认值(例如 null
对于 string
、0
对于 int
、Guid.Empty
对于 Guid
等)。
注释
默认情况下,尝试将显式值插入 SQL Server IDENTITY 失败; 有关解决方法,请参阅这些文档。
若要为已配置为在添加或更新时生成的值的属性提供显式值,还必须按如下所示配置属性:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().Property(b => b.LastUpdated)
.ValueGeneratedOnAddOrUpdate()
.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);
}
无值生成
除了上述特定场景之外,属性通常没有配置值生成,这意味着应用程序需要负责始终提供一个值,以便将其保存到数据库中。 此值必须分配给新实体,然后才能将其添加到上下文中。
但是,在某些情况下,你可能希望禁用已按约定设置的值生成。 例如,int 类型的主键通常会隐式配置为在新增时生成值(例如 SQL Server 上的标识列)。 可以通过以下方法禁用此项:
public class Blog
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int BlogId { get; set; }
public string Url { get; set; }
}