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


    生成值

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

    1. 默认值

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

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

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

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

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Created)
            .HasDefaultValueSql("getdate()");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. 计算列

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

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

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

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

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

    注意:
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    // 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    警告:
    与默认值或计算值不同,我们没有指定如何生成值;因为这取决于所使用的数据库提供程序。数据库提供程序可能会自动为某些属性类型设置值生成,但其他类型可能需要你手动设置值的生成方式。
    例如,在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()");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    请确保选择适当的函数,因为可能存在多个函数(例如: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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    运维开发详解
    数据结构与算法:配对堆
    MyBatisPlus中的TypeHandler
    基于Qt 的CAN Bus实现
    Kyligence 出席华为全球智慧金融峰会,加速拓展全球市场
    ArcObjects SDK开发 011 RasterLayer
    ECharts数据可视化项目【5】
    【Go blog】Govulncheck v1.0.0 发布了!
    10种有用的Linux Bash_Completion 命令示例
    JVM运行时数据区
  • 原文地址:https://blog.csdn.net/BadAyase/article/details/125560616