EF Core实体类配置

约定大于配置

主要的约定规则
规则1:数据库表名采用上下文类中对应的DbSet的属性名。
规则2:数据库表列的名字采用实体类属性的名字,列的数据库类型采用和实体类属性类型兼容的类型。比如在SQL Server中,string类型对应nvarchar,long类型对应bigint.
规则3:数据库表列尔可空性取决于对应实体类熟悉的可空性。
规则4:名字为Id的属性为主键,如果主键为short、int或者long类型,则主键默认采用自动增长类型的列。

Data Annotation(数据注解)

Data Annotation指的是可以使用.NET提供的Attribute对实体类、属性等进行标注的方式来实现实体类配置。比如通过[Table(“T_Books”)],我们可以把实体类对应的表名配置为T_Books;

1
2
3
4
5
6
7
8
9
//指定表名
[Table("T_Cats")]
class Cat
{
public int Id { get; set; }//主键
[Required]//非空
[MaxLength(20)]//最大长度
public string Name { get; set; }//姓名
}

优点:简单 | 缺点:耦合

Fluent API

Fluent API属于官方的推荐用法

Fluent API能够更好地进行职责分离。实体类只能负责进行抽象地描述,不涉及与数据库相关的细节,所有和数据库相关的细节被放到配置类中,这样我们能更方便地进行大型项目的管理。

2.Fluent API的功能更强大。Fluent API几乎能实现Data Annotation的所有功能,而Data Annotation则不支持Fluent API的一些功能。

Fluent API 基本配置

视图与实体类映射

可以用下面的代码把blogsView这个数据库中的视图和Blog实体类进行映射:

1
modelBuilder.Entity<Blog>().ToView("blogView");

排除属性映射

一个实体类的所有属性都会映射到数据库表中,如果想让EF Core忽略一个属性,就可以用lgnore配置。

1
modelBuilder.Entity<Blog>().Ignore(b=>b.Name2);

数据库表列名

数据库表中的列名默认和属性名一样,我们可以使用HasColumnName方法配置一个不同的列名。

1
modelBuilder.Entity<Blog>().Property(b=>b.BlogId).HasColumnName("blog_id");

列数据类型

EF Core默认会根据实体类的属性类型、最大长度等确定字段的数据类型,我们可以使用HasColumnType为列指定数据类型。

1
builder.Property(e=>e.Tilte).HasColumnType("nvarchar(200)");

主键

EF Core默认把名字为Id或者“实体类型+id”的属性作为主键,我们可以用HasKey配置其他属性作为主键。

1
modelBuilder.Entity<Student>().HasKey(c=>c.Number);

索引

EF Core中可用HasIdex方法配置索引。

1
modelBuilder.Entity<Blog>().HasIndex(b=>b.Url);

EF Core也支持多个属性组成的复合索引,只要给HasIndex方法传递由一个或多个属性的名字组成的匿名类对象即可。

1
modelBuilder.Entity<Person>().HasIndex(p=>new{p.FirstName,p.LastName});

EF Core中定义的索引不是唯一索引,我们可以用IsUnique方法把索引配置为唯一索引。我们还可以用IsClustered方法把索引设置为聚集索引。

主键类型选择并不简单

在数据库设计中,对于主键类型来讲,有自动自增(简称自增)的long类型和Guid类型两种常用的方案。

普通自增

自增long类型的使用非常简单,所有主流数据库系统都内置了对自增列的支持,新插入的数据库会由数据库自动赋予一个新增的、不重复的主键值。自增long类型占用磁盘空间小,可读性强,但是自增long类型的主键在数据库迁移以及分布式系统(分布表、数据库集群)中使用起来比较麻烦,而且在高并发插入的时候性能比较差。
由于自增列的值一般都是由数据库生成的,因此无法提前获得新增数据行的主键值,我们需要把数据保存到数据库之后才能获得主键的值。

1
2
3
4
5
6
Dog dog=new Dog();
dog.Name = "Trump";
Console.WriteLine(dog.Id);
ctx.Dogs.Add(dog);
await ctx.SaveChangesAsync();
Console.WriteLine(dog.Id);

Guid算法

Guid算法使用网卡的MAC地址,时间戳等信息生成一个全球唯一的ID。由于Guid的全球唯一性,它适用于分布式系统,在进行多数据库数据合并的时候很方便,因此我们也可以用Guid类型作为主键。
值得注意的是,由于Guid算法生成的值是不连续的,(即使是SQLServer中NewSequentialId函数生成的Guid也不能根本解决这个问题),因此我们在使用Guid类型作为主键的时候,他将会导致新插入的每条数据都要经历查找何时插入位置的过程,在数据量大的时候将会导致非常糟糕的数据插入性能。

1
2
3
4
5
6
7
8
Rebbit r = new Rebbit();
r.Id = Guid.NewGuid();
r.Name = "YOUXIANYU";
Console.WriteLine(r.Id);
ctx.Rebbits.Add(r);
Console.WriteLine(r.Id);
await ctx.SaveChangesAsync();
Console.WriteLine(r.Id);

其他数据库迁移命令

Update-database 其他参数

可以用Update-database XXX 把数据库回滚到XXX迁移脚本之后的状态。注意,这个命令只能把当前连接的数据库进行回滚,因此迁移脚本仍然存在。

删除迁移脚本

可以用Remove-migration命令删除最后一次的迁移脚本。

生产迁移脚本

EF Core中提供了Script-Migration命令来根据迁移代码生成SQL脚本,在【程序包管理器控制台】中输入Script-Migration并执行,一个包含完整的数据库操作脚本的SQL文件就会被创建和打开。

如果生产数据库已经处于某个迁移版本的状态,那么我们可以生成这个版本D到版本F的SQL脚本:Script-Migration D F。

还可以使用context.Database.Migrate()代码来对程序当前连接的数据库进行迁移。这种方式是直接在代码中完成数据库迁移,很多公司的安全审计要求提供的是明文的SQL语句。

查询EF Core生成的SQL语句

使用简单日志查看SQL语句

只要在上下文的OnConfiguring方法中调用optionsBuilder类的LogTo方法,查询SQL语句,输出到控制台。

1
2
3
4
5
6
7
8
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//连接数据库
base.OnConfiguring(optionsBuilder);
string connstr = "Server=localhost;Database=student;Trusted_Connection=True;MultipleActiveResultSets=true;Encrypt=false;";
optionsBuilder.UseSqlServer(connstr).LogTo(Console.WriteLine);
optionsBuilder.LogTo(message => Console.WriteLine(message),LogLevel.Information);
}

记录到文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private readonly StreamWriter _logStream = new StreamWriter("mylog.txt", append: true);

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(_logStream.WriteLine);

public override void Dispose()
{
base.Dispose();
_logStream.Dispose();
}

public override async ValueTask DisposeAsync()
{
await base.DisposeAsync();
await _logStream.DisposeAsync();
}