1. 该选择什么数据库
EF Core支持所有主流的数据库,包括Microsoft SQL Server、Oracle、MySQL、PostgreSQL、SQLite等数据库,而且EF Core的接口标准是开放的,只要按照标准去实现EF Core Database Provider,就可以对其他数据库添加EF Core的支持。
由于EF Core要求数据库提供对应的EF Core数据库提供程序,否则不能使用,因此在选择数据库的时候,一定要研究是否有和对应数据库匹配的、完善的EF Core数据库提供程序。
EF Core 对 Microsoft SQL Server的支持非常全面,bug也非常少,有一些新特性只有在Microsoft SQL Server中才支持。
当然Microsoft SQL Server服务器的成本是相对比较高的,因此对于成本敏感的项目,也可以使用MySQL、PostgreSQL等数据库。
无论用哪种数据库,EF Core的用法几乎是一模一样的。
不同的EF Core数据库提供程序的质量参差不齐,除了微软官方的Microsoft SQL Server的EF Core数据库提供程序之外,还存在着很多第三方的EF Core数据库提供程序,它们对于EF Core的支持大部分是一致的,但是会有细微的差别。比如在使用MySQL开发项目的时候,一部分在Microsoft SQL Server中支持的EF Core用法不被MySQL支持的问题,这些差异一般都有解决的方法。
因此,在EF Core中选择非Microsoft SQL Server数据库可能会遇到细微差别的问题,但是总体来讲难度可控。
2. EF Core环境搭建
无论是在控制台项目中还是在ASP.NET Core中,EF Core的用法都是一样的。
EF Core用于将对象和数据库中的表进行映射,因此在进行EF Core开发的时候,需要创建C#类(实体类)和数据库表两项内容。
在经典的EF Core使用场景下,由开发人员编写实体类,然后EF Core可以根据实体生成数据库表。
下面将会通过在数据库中保存书的信息来演示EF Core的使用。
- 新建一个控制台项目,然后创建Book实体类
public class Book
{
/// <summary>
/// 主键
/// </summary>
public long Id { get; set; }
/// <summary>
/// 标题
/// </summary>
public string? Title { get; set; }
/// <summary>
/// 发布日期
/// </summary>
public DateTime PubTime { get; set; }
/// <summary>
/// 单价
/// </summary>
public double Price { get; set; }
/// <summary>
/// 作者名字
/// </summary>
public string? AuthorName { get; set; }
}
- 为项目安装NuGet包Microsoft.EntityFrameworkCore.SqlServer,然后创建一个实现了IEntityTypeConfiguration接口的实体类的配置类BookEntityConfig,它用于配置实体类和数据库表的对应关系
public class BookEntityConfig : IEntityTypeConfiguration<Book>
{
public void Configure(EntityTypeBuilder<Book> builder)
{
builder.ToTable("T_Books");
builder.Property(e => e.Title).HasMaxLength(50).IsRequired();
builder.Property(e => e.AuthorName).HasMaxLength(20).IsRequired();
}
}
我们需要通过接口类型IEntityTypeConfiguration的泛型参数类指定这个类要对哪个实体类进行配置,然后在Configure方法中对实体类和数据库表的关系做详细的配置。
其中builder.ToTable(“T_Books”)表示这个实体类对应数据库中名字为T_Books的表。
这里没有配置各个属性在数据库中的列名和数据类型,EF Core将会默认把属性的名字作为列名,并且根据属性的类型来推断数据库表中各列的数据类型。
- 创建一个继承自己DbContext类的BookDbContext类
public class BookDbContext:DbContext
{
public DbSet<Book> Books { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connstr = "Server=.;Database=demo1;Trusted_Connection=True";
optionsBuilder.UseSqlServer(connstr);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}
BookDbContext中的Books属性对应数据库中的T_Books表,对Books的操作会反映到数据库的T_Books表中。
我们把这样继承自DbContext的类叫做”上下文”。
OnConfiguring方法用于对程序要连接的数据库进行配置,其中connstr变量的值表示程序要连接本地SQL Server数据库中的名字为demo1的数据库。
如果要连接其他服务器中的SQL Server数据库或者指定数据库的用户名、密码,请查询微软文档中关于连接字符串的格式要求。
至此,所有主干C#代码完成。
下面我们开始创建程序对应的数据库和数据库表。
在传统软件开发的流程中,数据库表的创建是由开发人员手动完成的,而在使用EF Core的时候,我们可以从实体类的定义中自动生成数据库表。
这样开发人员可以专注于实体类模型的创建,而创建数据库表这样的事情就交给EF Core完成。
这种先创建实体类再生成数据库表的开发模式叫做”模型驱动开发”,区别于先创建数据库表后创建实体类的”数据驱动开发”。
EF Core这种根据实体类生成数据库表的操作也被叫做”迁移”(migration)。
为了使用 EF Core生成数据库的工具,我们需要通过NuGet为项目安装Microsoft.EntityFrameworkCore.Tools包,否则执行Add-Migration等命令的时候会提示错误信息。
安装完成后,在程序包管理器控制台中执行如下命令:Add-Migration InitialCreate。
上述命令如果执行成功,程序包管理器控制台中将会输出如下
![](https://ichistudio.cn/wp-content/uploads/2024/04/图片-5.png)
Add-Migration命令会自动在项目的Migrations文件夹中生成C#代码
![](https://ichistudio.cn/wp-content/uploads/2024/04/图片-6.png)
打开20240404225648_InitialCreate.cs这个文件,文件的部分内容如下
![](https://ichistudio.cn/wp-content/uploads/2024/04/图片-7-1024x389.png)
可以看到,这个文件中包含用来创建数据库表的表名、列名、列数据类型、主键等的代码。
上面的代码还没有执行,它们需要被执行后才会应用到数据库,因此我们接着在程序包管理器控制台中执行Update-database命令编译并且执行数据库迁移代码。
如果Update-database命令执行成功,就会在程序包管理器控制台中看到以下结果
![](https://ichistudio.cn/wp-content/uploads/2024/04/图片-8.png)
查看SQL Server数据库,我们可以发现数据库demo1及数据库表T_Books已经创建成功,数据库表T_Books的结构也和实体类中配置得一致
![](https://ichistudio.cn/wp-content/uploads/2024/04/图片-9.png)
至此,EF Core中实体类的定义以及根据实体类生成数据库修改操作的迁移已经完成。
下面我们开始使用定义好的实体类对数据库数据进行操作。
3. 插入数据
BookDBContext类中的Books属性对应数据库中的T_Books表,Books属性是DbSet<Book>类型的。
因此我们只要操作Books属性,就可以向数据库中增加数据,但是通过C#代码修改Books属性中的数据只是修改了内存中的数据,对Books属性做修改后,还需要调用异步方法SaveChangesAsync把修改保存到数据库。
其实DbContext中也有同步的保存方法SaveChanges,但是采用异步方法通常能提示系统的并发处理能力,因此我们推荐使用异步方法。
插入图书数据
using BookDbContext bookDbContext = new BookDbContext();
var b1 = new Book { AuthorName = "IHCI", Title = "KUAC" };
var b2 = new Book { AuthorName = "COX", Title = "DAOFAZIR" };
var b3 = new Book { AuthorName = "WOR", Title = "ADNKN" };
var b4 = new Book { AuthorName = "ADNKD", Title = "WIJQE", Price = 2000 };
var b5 = new Book { Price = 8000, AuthorName = "WEWE", Title = "AWEQA" };
bookDbContext.Books.Add(b1);
bookDbContext.Books.Add(b2);
bookDbContext.Books.Add(b3);
bookDbContext.Books.Add(b4);
bookDbContext.Books.Add(b5);
await bookDbContext.SaveChangesAsync();
由于BookDbContext的父类DbContext实现了IDisposable接口,因此BookDbContext对象需要使用using代码块进行资源的释放。
上面的代码执行成功后,我们就可以在数据库中看到刚才插入的数据了。
![](https://ichistudio.cn/wp-content/uploads/2024/04/图片-10.png)
4. 查询数据
Books属性和数据库中的T_Books表对应,Books属性是DbSet<Book>类型的,而DbSet实现了IEnumerbable<T>接口,因此我们可以使用LINQ操作对DbSet进行数据查询。
查询所有的书以及价格高于800元的书
using BookDbContext db = new();
await Console.Out.WriteLineAsync("所有书:");
foreach (var bo in db.Books)
{
await Console.Out.WriteLineAsync($"Id={bo.Id},Title={bo.Title},Price={bo.Price}");
}
await Console.Out.WriteLineAsync("价格高于800元的书");
IEnumerable<Book> books = db.Books.Where(b=>b.Price>800);
foreach (var bo1 in books)
{
await Console.Out.WriteLineAsync($"Id={bo1.Id},Title={bo1.Title},Price={bo1.Price}");
}
既然可以在EF Core中执行LINQ操作,那么我们就可以使用LINQ进行更多复杂的数据查询。
Single、FirstOrDefault查询
using BookDbContext db = new();
Book b1 = db.Books.Single(b=>b.AuthorName =="IHCI");
await Console.Out.WriteLineAsync($"Id={b1.Id},Title={b1.Title},Price={b1.Price}");
Book? b2 = db.Books.FirstOrDefault(b => b.Id == 10);
if (b2 == null)
{
await Console.Out.WriteLineAsync("没有ID=9的数据");
}else
{
await Console.Out.WriteLineAsync($"Id={b2.Id},Title={b2.Title},Price={b2.Price}");
}
我们也可以使用OrderBy方法对数据进行排序
OrderBy排序
using BookDbContext db = new();
IEnumerable<Book> books = db.Books.OrderByDescending(b => b.Price);
foreach (var item in books)
{
await Console.Out.WriteLineAsync($"Id={item.Id},Title={item.Title},Price={item.Price}");
}
我们也可以使用GroupBy方法对数据进行分组。
GroupBy分组
using BookDbContext db = new();
var groups = db.Books
.GroupBy(b => b.AuthorName)
.Select(g => new {AuthorName = g.Key,BooksCount = g.Count(),MaxPrice = g.Max(b=>b.Price)});
foreach (var g in groups)
{
await Console.Out.WriteLineAsync($"作者:{g.AuthorName},最高价格:{g.MaxPrice},图书数量:{g.BooksCount}");
}
5. 修改和删除数据
使用EF Core,还可以对已有的数据进行修改、删除操作。
常规来讲,如果要对数据进行修改,我们首先需要把要修改的数据查询出来,然后对查询出来的数据进行修改,再执行SaveChangeAsync保存修改即可。
修改数据
using BookDbContext db = new BookDbContext();
var n = db.Books.FirstOrDefault(B => B.AuthorName == "IHCI");
n.AuthorName = "ICHI";
return await db.SaveChangesAsync();
同样,要对数据进行删除,我们要先把待删除的数据查询出来,然后调用DbSet或者DbContext的Remove方法把数据删除,再执行SaveChangeAsync方法保存结果到数据库。
删除数据
using BookDbContext db = new BookDbContext();
var a = db.Books.FirstOrDefault(b => b.AuthorName == "ICHI");
if (a!=null)
{
db.Remove(a);
}
return await db.SaveChangesAsync();
值得注意的是,无论是上面的修改数据的代码还是删除数据的代码,都是要先执行数据的查询操作,把数据查询出来,再执行修改或者删除操作。
这样在EF Core的底层其实发生了先执行Select的SQL语句,然后执行Update或者Delete的SQL语句。