EFCore学习笔记(5)——生成值

19 篇文章 0 订阅

生成值

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

1. 默认值

在关系数据库中,可以用默认值去配置列;如果插入的一行没有设置该列的值,那么就会使用默认值。

可以用以下方式给属性配置默认值:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()		// 获取Blog实体
        .Property(b => b.Rating)	// 取实体的Rating属性
        .HasDefaultValue(3);		// 设其默认值为3
}

还可以指定SQL片段(SQL Fragment)来计算默认值:

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

2. 计算列

在大多数关系数据库中,列可配置为在数据库中计算它的值,这时通常会使用一个引用其他列的表达式:

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

上面代码创建了一个虚拟计算列,每次从数据库中获取它的值时,都会计算它的值。你还可以指定存储一个计算列(有时也称持久化),意思是每次更新行时都会被计算,并与常规列一起存到磁盘上。

持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。
这个概念虽然听说过,但是没了解过,这边简单记一下,持久化常用于本地保持对象。比如说,你在云端做了些操作,然后关机。下次打开电脑,再访问云端时,还是从上次的操作的界面开始,这是因为本地存了状态。
持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。

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

注意:
EF Core 5.0增加了对创建存储计算列的支持。

3. 主键

按照约定(EF Core的约定或者说EF Core的规定),在应用程序没有提供值的情况下,short、int、long或Guid类型的非复合主键被设置为为插入的实体生成值。这通常由数据库提供程序负责配置;例如,SQL Server中的数字主键会自动设置到IDENTITY列。

4. 显式地配置值生成

我们可以从上文中看到,EF Core可以自动为主键设置值生成——但有时我们也想为非键属性做同样的事。你可以按以下方式配置任意属性来为插入实体生成值:

// 1. 数据标注
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public DateTime Inserted { get; set; }
}
// 2. fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Inserted)
        .ValueGeneratedOnAdd();
}

类似的,也可以配置属性来使其在添加或更新时生成值:

// 1. 数据标注
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime LastUpdated { get; set; }
}
// 2. fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.LastUpdated)
        .ValueGeneratedOnAddOrUpdate();
}

警告:
与默认值或计算值不同,我们没有指定如何生成值;因为这取决于所使用的数据库提供程序。数据库提供程序可能会自动为某些属性类型设置值生成,但其他类型可能需要你手动设置值的生成方式。
例如,在SQL Server上,当GUID属性配置为在添加时生成值时,数据库提供程序就会自动执行生成客户端侧生成值,使用算法生成最佳序列的GUID值。但是,在DataTime属性上指定ValueGeneratedOnAdd不会有任何效果。
类似地,配置为(在添加或更新时生成并标记为并发标记的)byte[]属性使用rowversion数据类型设置,以便在数据库中自动生成值。

注意:
根据使用的数据库提供程序,值可以由EF在客户端侧生成,也可以在数据库中生成。如果该值由数据库生成,那么当你添加实体到上下文时,EF可能会分配一个临时值;这个临时值会被SaveChanges()期间数据库生成的值给替换掉。

5. Date/time值生成

有个常见的请求是拥有一个数据库列,该列包含首次插入该列的日期/时间(在添加时生成),或在最后更新该列时的日期/时间(在添加或更新时生成)。因为有许多策略都可以做到这一点,所以EF Core提供者通常不会自动设置日期/时间值生成——你必须自己配置。

5.1. 创建时间戳

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

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

请确保选择适当的函数,因为可能存在多个函数(例如:GETDATE() vs. GETUTCDATE())

5.2. 更新时间戳

尽管存储计算列似乎是管理最后更新时间戳的好方案,但是数据库通常不允许在计算列中指定GETDATE()等函数。作为替代方案,你可以设置一个数据库触发器来达到相同的效果:

CREATE TRIGGER [dbo].[Blogs_UPDATE] ON [dbo].[Blogs]
    AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    IF ((SELECT TRIGGER_NESTLEVEL()) > 1) RETURN;

    DECLARE @Id INT

    SELECT @Id = INSERTED.BlogId
    FROM INSERTED

    UPDATE dbo.Blogs
    SET LastUpdated = GETDATE()
    WHERE BlogId = @Id
END

6. 覆盖值生成

尽管配置属性是为了值生成,但在许多情况下你仍然需要显式地为其指定值。这是否会真的起作用取决于已经配置的特定值的生成机制;虽然可以指定显式值来代替使用列的默认值,但这同样无法用计算列来完成。

要用一个显式值来覆盖生成值,只需要将属性设置为任何不是该属性类型的CLR默认值的值(string的null,int的0,Guid的Guid.Empty等)。

注意:
默认情况下,试图将显式值插入SQL Server IDENTITY会失败。

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

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

7. 无值生成

除了上述的特定场景外,属性通常没配置值生成;这意味着始终提供一个值来存到数据库中取决于应用程序。在新实体添加到上下文之前,该值必须被分配给新的实体。

可是,有些情况下,你也许希望禁用按约定设置的值生成。例如,一个int类型的主键通常会隐式配置为添加时生成的值(例如SQL Server上标识列)。你可以通过以下方式禁用它:

// 1. 数据标注
public class Blog
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int BlogId { get; set; }

    public string Url { get; set; }
}
// 2. fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.BlogId)
        .ValueGeneratedNever();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值