EFCore 支持几乎所有类型的主键,例如 int、short、long 类型的数字主键,也支持 Guid、string、byte[] 等类型的特殊主键,在 EFCore 中,数字主键自动自增,而 Guid 类型的主键也会自动生成,程序员不一定要手动填写。

当然,上述用 C# 类型描述主键,最终在生成数据库时,C# 类型都会被映射到数据库中兼容的类型,但不同的数据库系统支持不同的数据类型,可能存在一个 C# 类型在该数据库中找不到对应兼容的数据类型,这个时候生成数据库就会失败。

在实体定义中,以 Id 或者 <TypeName>Id 为名字的属性会自动被设定为主键,也可以手动通过 EntityTypeBuilder.HasKey 方法来设定主键。

自增主键优点是简单,缺点主要在数据库迁移和并发上比较麻烦。在分布式系统中,如果多个数据库分别采用自增主键,那么在合并多个数据库时,主键就会出现重复的情况。在并发过程中,由于主键是逐个递增的,为了防止主键重复,插入数据时需要加锁,这就导致了自增主键的并发性能较低。

而使用 Guid 主键的优点则是并发性能比较高,因为 Guid 主键是全局唯一的,并不会出现重复的问题,也易于合并。但缺点是 Guid 主键会占用较大的储存空间。而且 Guid 主键是不连续的,所以它在作为聚集索引时性能很低,一般不能作聚集索引。

在 EFCore 中,采用自增主键的实体不能手动赋值 Id,而应该保持默认值 0,由 EFCore 自动分配,分配是在 SaveChanges 时发生的:

var book = new Book { Title = "三国演义", Price = 50, Time = DateTime.Now };
Console.WriteLine(book.Id); //输出0
ctx.Books.Add(book);
Console.WriteLine(book.Id); //输出0
ctx.SaveChanges();
Console.WriteLine(book.Id); //输出3

而采用 Guid 主键的实体,既可以手动赋值,也可以由 EFCore 自动分配,且分配是在 Add 时发生的:

var store = new Store() { Name = "新华书店" };
Console.WriteLine(store.Id); //输出00000000-0000-0000-0000-000000000000
ctx.Stores.Add(store);
Console.WriteLine(store.Id); //输出2181bca4-a6ca-4d0b-8a8f-08da76b2d92f
ctx.SaveChanges();
Console.WriteLine(store.Id); //输出2181bca4-a6ca-4d0b-8a8f-08da76b2d92f

在 SQLServer,Guid 类型被映射为 uniqueidentifier 类型。也可以手动指定:

store.Id = Guid.NewGuid();

在实际使用中,要结合当前数据库使用什么系统、以及数据库是否需要高并发性能来选择自增主键还是 Guid 主键,例如有些版本的 MySQL 是强制主键为聚集索引的,此时使用 Guid 就得不偿失了。在特殊的情况下,也可以使用混合自增和 Guid 来作为主键,但并非将它们作为复合主键,只是将自增列作为物理上的数据库主键,而 Guid 列在物理上只是普通的列,但是在业务上当成主键来使用,这样可以结合自增主键和 Guid主键的优点。