前面学了关系数据库中键这个概念之后,再回过来看,EF Core中的键与关系数据库中的键可以说是一样的。
键(也称码、或者关键字)。
键是用作每个实体实例的唯一标识符的。EF中的大部分实体都有一个键,它会映射到关系数据库中的主键(primary key,简称PK)这个概念(还有一部分实体是可以没有键的,称为无键实体,这边不展开讲)。
实体还可以拥有除了主键外的其他键(候补键,或称候补码)。
按照约定,一个属性命名为Id或者<type name> Id就会被配置成实体的主键。
internal class Car
{
public string Id { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
internal class Truck
{
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>()
.HasKey(c => c.LicensePlate);
}
你还可以将多个属性配置为实体的主键——这也称为组合键。组合键只能用fluent API来配置;默认约定下,永远也不会给你设置一个组合键,你也无法通过数据标注来配置组合键。
// 1. fluent API配置组合键
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasKey(c => new { c.State, c.LicensePlate });
}
对于非组合的数字和GUID(全局唯一标识符,Globally Unique IDentifier)主键,EF Core会按照约定为你生成值。例如,SQL Server中的数字主键将自动设置为IDENTITY列。
按照约定,在关系数据库中主键会被创建为带有PK_<typename>名称的。你可以按照以下方式配置主键约束的名称:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasKey(b => b.BlogId)
.HasName("PrimaryKey_BlogId");
}
虽然EF Core支持使用任何基本类型(primitive type)的属性作为主键,包括string、Guid、byte[]等,但是不是所有的数据库都支持所有类型作为键的。在某些情况下,键值可以自动转换为支持的类型,其他情况下转换应手动进行。
在上下文添加新的实体时,键属性必须始终具有非默认值,但有些类型是由数据库来生成的。在那种情况下,为了跟踪目的而添加实体时,EF将尝试生成一个临时值。调用SaveChanges后,临时值会被由数据库生成的值给替换掉。
如果一个键属性的值由数据库生成并且在实体被添加时指定了一个非默认值,那么EF会假设实体已经存在于数据库中,并尝试更新它,而不是插入新实体。想避免这种情况,就要关闭值生成或查看怎样为生成的属性指定一个显式值。
除主键外,候补键是用作实体实例的候补唯一标识符的;它可作为一段关系的目标。当使用关系数据库时,这将映射到候补键列上的唯一索引/约束以及引用该列的一个或多个外键的概念。
如果你只想在一列上强制唯一性,就定义唯一索引而不是候补键。在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");
}