一、概述
数据库列的值可以通过多种方式生成:主键列通常是自动递增的整数,其他列具有默认值或计算值等。本文详细介绍使用 EF Core
配置值生成的各种模式。
二、默认值
在关系数据库中,可以为列配置默认值;如果插入的行没有该列的值,则将使用默认值。
可以在属性上配置默认值:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.state) .HasDefaultValue(true); }
可指定用于计算默认值的 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的非复合主键设置为针对插入的实体生成值。数据库提供程序通常负责必要的配置;
详细内容请于阅读聊聊EF中的键
五、显示配置值生成
EF Core会自动为主键设置值生成-但我们可能希望对非键属性 执行相同的操作。可以将任何属性配置为针对插入的实体生成其值,具体如下所示:
- 数据注释
public class Lessess { public int LessessId{get;set;} public string LessessCode{get;set;} public string LessessName{get;set;} //显示配置值生成 [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public DateTime Inserted{get;set;} }
- Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Lessess>() .Property(b => b.Inserted) .ValueGeneratedOnAdd(); }
同样,可以将属性配置为在添加或更新时生成其值:
- 数据注释
public class Lessess { public int LessessId{get;set;} public string LessessCode{get;set;} public string LessessName{get;set;} //显示配置值生成 [DatabaseGenerated(DatabaseGeneratedOption.Computed)] public DateTime Inserted{get;set;} }
- Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Lessess>() .Property(b => b.LastUpdated) .ValueGeneratedOnAddOrUpdate(); }
与默认值或计算列不同,再没有指定值的生成方式;这取决于所使用的数据库提供程序。数据库提供程序可能会自动为某些属性类型设置值生成,但其他属性类型可能需要你手动设置值的生成方式。
同样,配置为在添加或更新时生成值并标记为并发标记的byte[]属性将设置为rowversion数据类型,以便在数据库中自动生成值。但是指定ValueGeneratedOnAdd不起作用。
六、设置日期/时间值生成
常见的请求时获取一个数据列,其中包含第一次插入行的日期/时间(在添加时生成的值)或上次更新行的日期/时间(添加或更新时生成的值)。由于可通过各种策略执行此操作,因此EF Core提供程序通常不会为日期/时间列自动设置值生成-必须自动设置;
6.1 创建时间戳
若要将日期/时间列配置为包含行的创建时间戳,通常需要使用适当的SQL函数来配置默认值。
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Created) .HasDefaultValueSql("getdate()"); }
请确保选择适当的函数,因为可能存在多个函数(例如 GETDATE() 与 GETUTCDATE())。
6.2 更新时间戳
尽管存储计算列看起来非常适合管理上次更新时间戳,但数据库通常不允许在计算列中指定诸如GETDATE()之类的函数。作为替代方法,你可以设置一个数据库触发器来达到同样的效果。
七、替代值生成
尽管为属性配置了值生成,但在许多情况下,你仍然可以为其显式指定一个值。 此操作能否真正起作用取决于已配置的特定值生成机制;虽然你可以指定显式值而不是使用列的默认值,但不能对计算列执行相同的操作。
若要使用显示值替代值生成,只需将属性设置为该属性类型的CLR默认值(string为null,int为0,Guid为Guid.Empty,等等)以外的任意值。
若要为已配置为在添加或更新时生成值的属性提供显示值,必须按以下方式配置该属性:
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 User { [DatabaseGenerated(DatabaseGeneratedOption.None)] public int UserId { get; set; } public string LoginName { get; set; } }
- Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<User>() .Property(b => b.UserId) .ValueGeneratedNever(); }
九、总结
Entity Framework
提供多种在应用端生成值的能力。在实际开发中,需要考虑在数据库层还是在应用层完成这些值的自动匹配和完成值的生成。