生成的值

数据库列的值可以通过多种方式生成:主键列通常是自动递增的整数,其他列具有默认值或计算值等。本页详细介绍使用 EF Core 配置值生成的各种模式。

默认值

在关系数据库中,可以为列配置默认值;如果插入的行没有该列的值,则将使用默认值。

可以在属性上配置默认值:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Rating)
        .HasDefaultValue(3);
}

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

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

计算列

在大多数关系数据库中,可以将列配置为在数据库中计算其值,并且通常使用引用其他列的表达式:

modelBuilder.Entity<Person>()
    .Property(p => p.DisplayName)
    .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");

以上命令将创建一个虚拟计算列,每次从数据库中提取时都会计算其值。 你也可以将计算列指定为存储(有时称为持久化)计算列,这意味着系统会在每次更新行时计算该列,并将其与常规列一起存储在磁盘上:

modelBuilder.Entity<Person>()
    .Property(p => p.NameLength)
    .HasComputedColumnSql("LEN([LastName]) + LEN([FirstName])", stored: true);

主键

按照约定,如果应用程序未提供值,则将类型为 short、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 值。 但是,在 DateTime 属性上指定 ValueGeneratedOnAdd 将不起作用(请参阅以下有关 DateTime 值生成的部分)。

同样,配置为在添加或更新时生成值并标记为并发标记的 byte[] 属性将设置为 rowversion 数据类型,以便在数据库中自动生成值。 但是,指定 ValueGeneratedOnAdd 不起作用。

有关它支持的特定值生成技术,请参阅提供商的文档。 可以在此处找到 SQL Server 值生成文档。

日期/时间值生成

常见的请求是获得一个数据库列,其中包含第一次插入行的日期/时间(在添加时生成的值)或上次更新行的日期/时间(添加或更新时生成的值)。 由于可通过各种策略执行此操作,因此 EF Core 提供程序通常不会为日期/时间列自动设置值生成 - 你必须自行配置。

创建时间戳

若要将日期/时间列配置为包含行的创建时间戳,通常需要使用适当的 SQL 函数来配置默认值。 例如,在 SQL Server 上,你可以使用以下命令:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    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 默认值(stringnullint0GuidGuid.Empty,等等)以外的任意值。

注意

默认情况下,尝试将显式值插入 SQL Server IDENTITY 会失败;请参阅这些文档了解解决方法

若要为已配置为在添加或更新时生成值的属性提供显式值,还必须按以下方式配置该属性:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().Property(b => b.LastUpdated)
        .ValueGeneratedOnAddOrUpdate()
        .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);
}

无值生成

除了上述特定方案外,属性通常不会配置值生成;这意味着,始终由应用程序提供要保存到数据库的值。 必须先将此值分配给新实体,然后才能将新实体添加到上下文中。

但是,在某些情况下,你可能希望禁用按约定设置的值生成。 例如,int 类型的主键通常隐式配置为 value-generated-on-add(例如 SQL Server 上的 identity 列)。 你可以通过以下命令禁用此功能:

public class Blog
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int BlogId { get; set; }

    public string Url { get; set; }
}