public class BookMap : EntityMappingConfiguration<Book> { Map(EntityTypeBuilder<Book> b) { b.ToTable(); b.HasKey(k => k.Id); b.HasAlternateKey(k => k.UserId); } }
最后监控得到如下语句:
看到没,为用户Id配置了唯一约束:
CONSTRAINT [AK_Book_UserId] UNIQUE ([UserId])
所以我们得出结论:通过可选键我们可以创建唯一约束来除主键之外唯一标识行。
主体键映射(Principal Key)如果我们想要一个外键引用一个属性而不是主键,此时我们可以通过主体键映射来进行配置,此时配置主体键映射背后实际上自动将其设置为一个可选键。这个就不用我们多讲了。
好了到此为止我们讲完了键映射,接下来我们再来讲述属性映射:
属性映射对于C#中string类型若我们不进行配置,那么在数据库中将默认设置为NVARCHAR并且长度为MAX且是为可空,如下:
若我们需要设置其长度且为非空,此时需要进行如下配置:
b.Property(p => p.Name).IsRequired().HasMaxLength(50);
通过HaxMaxLength方法来指定最大长度,通过IsRequired方法来指定为非空。但是此时问题来了,数据库类型对于string有VARCHAR、CHAR、NCAHR类型,那么我们应当如何映射呢?比如对于VARCHAR类型,在EF Core中对于数据库列类型我们可以通过 HasColumnType 方法来进行映射,那么假设对于数据库类型为VARCHAR长度为50且为非空,我们是否可以进行如下映射呢?
b.Property(p => p.Name) .IsRequired() .HasColumnType() .HasMaxLength(50);
通过上述迁移出错,我们修改成如下才正确:
b.Property(p => p.Name) .IsRequired() .HasColumnType("VARCHAR(50)");
解决一个,又来一个,那么对于枚举类型我们又该进行如何映射呢,枚举对应数据库中的类型为TINYINT,我们进行如下设置:
public class Product { public int Id { get; set; } public string Name { get; set; } public Type Type { get; set; } public IEnumerable<ProductCategory> ProductCategorys { get; set; } } public enum Type { [Description()] General = 0, [Description()] Insurance = 1 }
public class ProductMap : EntityMappingConfiguration<Product> { Map(EntityTypeBuilder<Product> b) { b.ToTable(); b.HasKey(k => k.Id); b.Property(p => p.Type) .IsRequired() .HasColumnType("TINYINT"); } }
此时则对应生成我们想要的类型:
CREATE TABLE [Product] ( [Id] int NOT NULL IDENTITY, [Name] nvarchar(max), [Type] TINYINT NOT NULL, CONSTRAINT [PK_Product] PRIMARY KEY ([Id])
【注意】:此时将其映射成枚举没毛病上述已经演示,但是当我们获取数据时将TINYINT转换成枚举时将出现如下错误:
说到底TINYINT对应C#中的byte类最后尝试将其转换为int才会导致转换失败,所以在定义枚举时记得将其继承自byte,如下才好使:
public enum Type : byte { [Description()] General = 0, [Description()] Insurance = 1 }
讲完如上映射后,我们再来讲讲默认值映射。 当我们敲默认映射会发现有两个,一个是HasDefaultValue,一个是HasDefaultValueSql,我们一起来看看到底如何用:
我们在Product类中添加Count字段:
public int Count { get; set; }
b.Property(p => p.Count).HasDefaultValue(0);
如上是对于int类型如上设置,如果是枚举类型呢,我们来试试:
b.Property(p => p.Type) .IsRequired() .HasColumnType().HasDefaultValue(0);
此时迁移将出现如下错误:
也就是说无法将枚举值设置成int类型,此时我们应该利用HasDefaultValueSql来映射:
b.Property(p => p.Type) .IsRequired() .HasColumnType().HasDefaultValueSql();
对于默认值映射总结起来就一句话:对于C#中的类型和数据库类型一致的话用HasDefaultValue,否则请用HasDefaluValueSql。
【注意】:对于字段类型映射有一个奇葩特例,对于日期类型DateTime,在数据库中也存在其对应的类型datetime,但是如果我们不手动指定类型会默认映射成更精确的日期类型即datetime2(7)。
我们在Product类中添加创建时间列,如下:
public DateTime CreatedTime { get; set; }
此时我们不指定其映射类型,此时我们看到在数据库中的类型为datetime2(7)
当然以上映射也没什么问题,但是对于大部分对于日期类型都是映射成datetime且给定默认时间为当前时间,所以此时需要手动进行配置,如下:
b.Property(p => p.CreatedTime) .HasColumnType() .HasDefaultValueSql();
说完默认值需要注意的问题,我们再来讲讲计算列的映射,在EF Core中对于计算列映射,在之前版本为ForSqlServerHasComputedColumnSql,目前是HasComputedColumnSql。例如如下这是计算列:
b.Property(p => p.Name) .IsRequired() .HasComputedColumnSql();
其中还有关于列名自定义的方法(HasColumnName),主键是否自动生成(ValueGeneratedOnAdd)等方法以及行版本(IsRowVersion)和并发Token(IsConcurrencyToken)。还有设置索引的方法HasIndex