一、Keys(键)
前面学了关系数据库中键这个概念之后,再回过来看,EF Core中的键与关系数据库中的键很相似。
键(也称码、或者关键字)。
键是用作每个实体实例的唯一标识符的。EF中的大部分实体都有一个键,它会映射到关系数据库中主键(primary key,简称PK)这个概念(部分实体是可以没有键的,称为无键实体,这边不展开讲)。
实体还可以有主键之外的其他键(候补键,或称候补码)。
1. 配置一个主键
按照约定,一个属性命名为Id或<type name> Id就会被配置成实体的主键。
internal class Car
{
// Id被配置为主键
public string Id { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
internal class Truck
{
// TruckId被配置为主键
public string TruckId { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
注意:
自身拥有的实体类型使用不同的规则来定义键。// ?
你可以按以下方式将单个属性配置为主键:
// 1. 数据标注
internal class Car
{
[Key]
public string LicensePlate { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
// 2. fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>() // 模型中取出Car实体
.HasKey(c => c.LicensePlate); // 使LicensePlate成为主键
}
你也可以将多个属性配置为实体的主键——这被称为组合键(有点超码的概念)。组合键只能用fluent API来配置;默认情况下,(EF)永远也不会给你设置一个组合键,你也无法通过数据标注来配置组合键。
// 1. fluent API配置组合键
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasKey(c => new { c.State, c.LicensePlate });
}
△2. 生成值
对于非复合的数字和GUID(全局唯一标识符,Globally Unique IDentifier)主键,EF Core会按照约定为你生成值。例如,SQL Server中的数字主键将自动设置为IDENTITY列。
3. 主键名称
按照约定,在关系数据库中主键会被创建为带有PK_<type name>名称的。你可以按以下方式配置主键约束的名称:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasKey(b => b.BlogId)
.HasName("PrimaryKey_BlogId");
}
△4. 键类型和值
虽然EF Core支持使用任何基本类型(primitive type)的属性作为主键,包括string、Guid、byte[]等,但并不是所有数据库都支持所有类型作为键的。在某些情况下,键值可以自动转换为支持的类型,否则转换应手动进行。
当在上下文中添加新实体时,键属性必须始终具有非默认值,但有些类型(的值)是由数据库来生成的。在那种情况下,为了跟踪目的而添加实体时,EF将尝试生成一个临时值。在调用SaveChanges后,临时值会被数据库生成的值给替换掉。
如果一个键属性的值由数据库生成并且在实体被添加时指定了一个非默认值,那么EF会假设实体已经存在于数据库中,并尝试更新它,而不是插入新实体。想避免这种情况,就要关闭值生成或查看怎样为生成的属性指定一个显式值。
5. 候补键(Alternate Keys)
你可以根据个人喜好称其为备用键、替换键等。
除主键外,候补键是用作实体实例的备用唯一标识符的;它可用作一个关系的目标。当使用关系数据库时,它将映射到一个概念,一个在候补键所在列上的唯一索引/约束和一个或多个引用该列的外键约束。
如果你只想在一列上强制唯一性,就定义唯一索引而不是候补键。在EF中,候补键是只读的,并且在唯一索引上提供额外语义,因为它们可以作为外键的目标。
候补键往往在需要时被引入,并且你不必手动配置它们。按照约定,当你标识一个非主键的属性来作为关系的目标时,会为你引入候补键。
internal class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts)
.HasForeignKey(p => p.BlogUrl)
.HasPrincipalKey(b => b.Url);
}
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public string BlogUrl { get; set; }
public Blog Blog { get; set; }
}
你可以配置单个属性为候补键:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasAlternateKey(c => c.LicensePlate);
}
你也可以配置多个属性为一个候补键(被称为组合候补键):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasAlternateKey(c => new { c.State, c.LicensePlate });
}
最后,按照约定,为候补键引入的索引和约束将被命名为AK_<type name>_<property name>(对于组合候补键<property name>会变成一个下划线分割的属性名列表)。你可以配置候补键的索引和唯一约束的名称。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasAlternateKey(c => c.LicensePlate)
.HasName("AlternateKey_LicensePlate");
}