目录
一、EF Core概述
Entity Framework Core(简称EF Core) 是.NET Core中的ORM (object relational mapping,对象关系映射) 框架。它可以让开发者面向对象的方式进行数据库操作。
1.1 什么是ORM?
对象:c#中的对象
关系:关系数据库
映射:关系数据库和c#对象之间搭建的一座桥梁
我们知道在.NET中可以用过ADO.NET连接数据库然后执行SQL语句来操作数据库中的数据。而ORM可以直接通过操作c#对象的方式操作数据库。
如下通过创建c#对象的方式把数据插入数据库,省去编写Insert语句:
User user = new User(){Name="li",Password="123456"};
orm.Save(user);
注意:ORM只是对ADO.NET的封装,ORM底层任然是通过ADO.NET来访问数据库
1.2 EF Core的性能怎么样
EF Core是把对c#对象的操作转换成SQL语句,由于SQL语句是自动生成的,所以如果使用不当就会产生低性能的数据库操作。
二、EF Core入门
EF Core用于将对象和数据库中的表进行映射,因此需要创建实体类和数据库表两项内容。
例如:使用EF Core通过在数据库中保存书的信息.
1、创建Book实体类
2、为项目安装NuGet包Microsoft.EntityFrameworkCore.SqlServer
3、创建一个实现了IEntityTypeConfiguration泛型接口的实体类的配置类BookEntityConfig,它用于配置实体类和数据库的对应关系。
很容易发现,这里对实体类进行配置的时候没有配置各个属性在数据库中的列名和数据类型,EF Core将会默认把属性的名字作为列.名,并且根据属性的类型来推断数据库表名各列的数据类型。
4、创建一个继承自DbContext 的TestDbContext类。
2.1 什么是Migration数据库迁移:
面向对象的ORM开发中,数据库不是程序员手动创建的,而是根据对象的定义变化,自动更新数据库中的表以及表结构的操作,叫作Migration(迁移)。
搭建Migration步骤:
- 使用NuGet安装Microsoft.EntityFrameworkCore.Tools。
- 在“程序包管理控制台”执行数据库迁移指令“Add-Migration InitialCreate”。(InitialCreate:当前的migration的名字) Add-migration作用:根据实体类及配置生成操作数据库的迁移代码
执行成功后多出了一个migration文件夹
- 在“程序包管理控制台”执行Update-Database,这样创建数据库的相关代码才会被执行,数据表才会生成。
注意:如果在使用该指令时,显示“证书链是由不受信任的颁发机构颁发的”,主需要在数据库配置处添加TrustServerCertificate=true即可,具体可看我的博文介绍:https://mp.csdn.net/mp_blog/creation/editor/132865793https://mp.csdn.net/mp_blog/creation/editor/132865793执行成功后我们发现此时数据库中已经生成了我们创建的T-Books表格:
当我们要修改数据库内容时 ,只需要修改相关实体类的内容,再次执行迁移指令Add-migration InitialCreate以及更新指令Update-Database即可,但是此时迁移指令的命名因为采用不同的名字。
2.2 EF Core数据的增删改查
如上述代码我们知道,Books属性对应数据库中的T_Books表,Books属性是DbSet<Book>类型的。因此只需要操作Books属性就可以向数据库中添加数据。但是通过c#代码修改Books属性种的数据只是修改了内存的数据,还需要调用异步方法SaveChangesAsync把修改的内容保存到数据库。
2.2.1 增加数据
此时刷新数据库,发现数据已经成功写入 。这样我们就成功实现不用编写SQL语句,而是通过创建对象和对象赋值的方法完成对数据库的操作。
2.2.2 查询数据
Books属性对应数据库中的T_Books表,Books属性是DbSet<Book>类型的,而DbSet实现了IEnumerable<T>接口,因此可以使用LINQ操作对DbSet进行数据查询。EF Core会把LINQ操作转换为SQL语句。
例如查询所有书籍以及使用条件查询:
using TestDbContext ctx = new TestDbContext();
Console.WriteLine("***所有书籍***");
foreach (Book b in ctx.Books)
{
Console.WriteLine($"Id={b.Id},Title={b.Title},Price={b.Price}");
}
Console.WriteLine("***所有价格高于80元的书籍***");
IEnumerable<Book> books2 = ctx.Books.Where(b => b.Price > 80);
foreach (Book b in books2)
{
Console.WriteLine($"Id={b.Id},Title={b.Title},Price={b.Price}");
}
也可以使用LINQ进行复杂的数据查询,例如Single,FirstOrDefault,OrderBy等,在这里不就演示了,感兴趣的可以自己去尝试哟。
如果对LINQ语法模糊的欢迎跳转到我的LINQ博文:https://mp.csdn.net/mp_blog/creation/editor/132326038https://mp.csdn.net/mp_blog/creation/editor/132326038
2.2.3 修改和删除数据
使用EF Core对已有数据进行修改,我们需要先把修改的数据查询出来,然后进行修改,再执行SaveChangesAsync保存修改即可。
修改数据库内容语法:
using TestDbContext ctx = new TestDbContext();
var b = ctx.Books.Single(b => b.Title == ".net");
b.AuthorName = "li";
await ctx.SaveChangesAsync();
删除数据库某条数据语法:
using TestDbContext ctx = new TestDbContext();
var b = ctx.Books.Single(b => b.Title == ".net");
ctx.Books.Remove(b);
await ctx.SaveChangesAsync();
注意:我们不论是修改还是删除数据,都要先执行数据的查询操作,把数据查询出来,在执行相应操作。这样EF Core的底层其实发生了先执行Select的SQL语句,再执行Update或者delete语句。
三、EF Core的实体类配置
3.1 约定大于配置
EF Core采用“约定大于配置”的设计原则,默认按照约定根据实体类以及DbContext的定义来实现和数据库表的映射配置,除非用户显示指定了配置规则。
主要规则总结:
- 数据库表名采用上下文类中对应的DbSet的属性名;
- 数据库表列的名采用实体类属性的名字,数据类型采用跟实体类兼容的数据类型;
- 数据库表列的可空取决于对应实体类属性的可空性;
- 名字为Id的属性为主键,如果主键为short,int或者long类型,主键默认采用自动增长;
3.2 EF Core两种配置方式
3.2.1 Data Annotation
Data Annotation(数据注释)指通过.NET提供的Attribute对实体类,属性等进行标注的方式来实现实体类配置。
基本使用方法:
[Table("T_Books")] //将实体类对应的表名配置为 T_Books
public class Book
{
public long Id { get; set; } //这是主键(EF Core默认约束规定)
[MaxLength(50)] //设置最大长度
[Required] //设置为不为空
public string Title { get; set; }
public DateTime PubTime { get; set; }
public double Price { get; set; }
[MaxLength(20)]
[Required]
public string AuthorName { get; set; }
}
3.2.2 Fluent API
就是前面讲解的BookEntityConfig模板,采用builder.ToTable("表名")的方法
3.2.3 两种方式的比较
看起来很容易发现使用Data Annotation方法更简单,只需要在实体上加入Attribute即可,不用像Fluent API一样写单独的配置类,但是Fluent API是官方推荐的用法,原因如下:
(1)Fluent API能够更好的进行职责划分,所有和数据库相关的内容都放在配置类中。
(2)Fluent API和Data Annotation可以同时使用,但是Fluent API优先级高于Data Annotation
3.3 Fluent API 的基本使用
(1)排除属性映射:默认情况下,一个实体类的全部属性都会映射到数据库表中,但是使用ignore配置可以让EF Core忽略一个属性。
ModelBuilder.Entity<Blog>().Ignore(b => b.name);
例:
(2)数据库表列名:数据库表中列名默认和属性名一样,我们可以使用HasColumnName方法配置一个不同的列名。
ModelBuilder.Entity<Blog>().Property(b => b.Id).HasColumnName(b_Id);
(3)列数据类型: EF Core会根据实体类类型,最大长度等确定字符的数据类型。我们可以使用HasColumnType为列指定数据类型。
builder.Property(b => b.Id).HasColumnType("varchar(5)");
(4)主键:EF Core会默认把名字为Id或者“实体类型+Id”的属性作为主键,我们可以使用HasKey配置其他属性为主键。
ModelBuilder.Entity<Blog>().HasKey(b => b.name);
(5)索引: EF Core中可以使用HasIndex方法配置索引
Fluent API还可以使用链式调用方法,但是在调用的时候需要返回值类型相同的才可以使用链式调用。VS已经提供了非常完善的代码提示机制,很容易就能知道某个方法能否使用链式调用。
3.4 EF Core主键类型的选择
在数据库设计中,对于主键的类型,有自动增长的long类型和Guid类型两种常用方案。
3.4.1 普通自增
优点:自增long类型的使用非常简单,所有主流数据库都内置了对自增列的支持。新插入的数据都会由数据库自动赋值一个新增的、不重复的主键。而且占用磁盘空间小,可读性强。
缺点:自增long类型的主键在数据库迁移以及分布式系统(如分库分表,数据库集群)中使用非常麻烦,而且在高并发插入的时候性能比较差。
由于自增列的值一般是由数据库自动生成的,因此无法提前获得新增数据行的主键值,我们需要把数据保存到数据库后才能获得主键值:
3.4.2 Guid算法
Guid算法使用网卡的MAC(medium access control,介质访问控制)地址、时间戳等信息生成一个全球唯一的ID。
优点:简单,高并发,全局唯一
缺点:磁盘占用空间大
注意:由于Guid算法生成的值是不连续的,因此不能把Guid类型的主键设置为聚集索引,因为聚集索引是按顺序保存主键的,在插入Guid类型主键的时候,它将导致新插入的每天数据都要经历查找合适位置的过程,当数据量特别大的时候性能就很特别糟糕。
在SQLServer中,可以设置主键为非聚集索引,但在MySQL中,如果我们使用InnoDB引擎,那么主键是强制聚集索引的,因此在SQL Server中我们使用Guid(也就是uniqueidentifier类型)作为主键一定不能把主键设置为聚集索引。
接下来演示一下Guid类型主键的用法:
(1)创建一个实体类Author:
(2)在TestDbContext类中增加一个DbSet属性(TestDbContext类像一个数据库,管理着多个DbSet。一般项目每增加一个实体类,都在对应的TestDbContext类中添加对应的DbSet属性):
(3)执行Add-Migration和Update-database生成相关数据表:
(4) 插入数据到数据库表中:
执行后:
四、关系配置
4.1 一对多
举例讲解:比如文章与评论就是一对多关系,一篇文章对应多条评论
定义实体类:
class Article
{
public long Id { get; set; } //主键
public string Title { get; set; }
public string Content { get; set; }
public List<Comment> Comments { get; set; } = new List<Comment>(); //此文章的多条评论
}
class Comment
{
public long Id { get; set; } //主键
public Article Article { get; set; } //评论属于那篇文章
public string Message { get; set; }
}
实体类配置(对于一对多的配置主要是倒数第四横排,语法是HasXXX....withXXX,其中XXX表示One or Many):
class ArticleConfig : IEntityTypeConfiguration<Article>
{
public void Configure(EntityTypeBuilder<Article> builder)
{
builder.ToTable("T_Articles");
builder.Property(a => a.Content).IsRequired().IsUnicode(true);
builder.Property(a =>a.Title).IsRequired().IsUnicode(true).HasMaxLength(255);
}
}
class CommentConfig : IEntityTypeConfiguration<Comment>
{
public void Configure(EntityTypeBuilder<Comment> builder)
{
builder.ToTable("T_Comments");
builder.HasOne<Article>(c =>c.Article).WithMany(a => a.Comments).IsRequired();
builder.Property(c =>c.Message).IsRequired().IsUnicode();
}
}
插入数据:
4.2 关联数据的获取
例如 把Id==1的文章以及评论输出:
4.3 实体类对象的关联追踪
我们可以不给Comments属性添加对象,而是给Comment对象的Article属性赋值的方式完成数据的插入。
Article a = new Article();
a.Title = "EF Core入门学习";
a.Content = ".net是一个十分伟大的平台";
Comment b1 = new Comment() { Message = "支持", Article = a };
Comment b2 = new Comment() { Message = "太酷啦", Article = a };
using TestDbContext ctx = new TestDbContext();
ctx.Comments.Add(b1)
ctx.Comments.Add(b2)
后续的一对多以及多对多就不展开讲解了,基本方法都差不多,后续时间充分的话再进行补充