Net6 控制台应用程序引入EFCore+CodeFirst
十年河东,十年河西。莫欺少年穷
学无止境,精益求精
最近再看Net5相关视频,看的过程中就想把看到的知识通过博客展示出来,因此就有了这篇博客
之前项目中用的DbFirst ,本篇开启CodeFirst,按照微软官方提供的说法,是希望大家都用CodeFirst
1、CodeFirst
1.1、新建控制台项目并引入如下程序集
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
1.2、编写实体
实体对应的是数据库的表

public class Book { public long id { get; set; } [Description("书的名称")] public string Title { get; set; } public DateTime Pubdate { get; set; } public double Price { get; set; } } public class Person { public long id { get; set; } [Description("姓名")] public string Name { get; set; } public string Age { get; set; } public string Address { get; set; } }
1.3、绑定实体和数据表的关系

public class BookConfig : IEntityTypeConfiguration<Book> { public void Configure(EntityTypeBuilder<Book> builder) { builder.ToTable("T_Books"); } } public class PersonConfig : IEntityTypeConfiguration<Person> { public void Configure(EntityTypeBuilder<Person> builder) { builder.ToTable("T_Persons"); builder.Property("Name").HasMaxLength(50).IsRequired(true).HasComment("我是字段备注:姓名"); } }
意味着Book实体对应的表为 T_Books
builder.Property 声明字段的一些规则,例如长度最大为50【HasMaxLength(50)】、字段为非空字段【IsRequired(true)】、设定字段备注【HasComment】等
如
// builder.ToView<Book>("视图名称"); //绑定视图的模式 //builder.Ignore(A => A.Pubdate);//Pubdate 字段是实体中的字段,但不会出现在Table中,例如默写通过计算得出的列或者属性 //builder.Property("Name").HasColumnName("BookName"); //配置列名 在数据表中会展示为BookName // builder.Property("Name").HasColumnType("varcahr(200)"); //配置列名的数据类型 改变默认的Varchar(MAX) 及 改变数据库的 NVVARCHAR(MAX) ,不存中文时用这个 //builder.Property("Name").HasDefaultValue("书"); 默认值 //builder.HasKey(A => A.Title);//修改默认主键配置 主键为 Title //builder.HasIndex(A => A.Price);//设置价格列为索引 // builder.HasIndex(A => new { A.Price, A.Pubdate });//设置复合索引 builder.HasIndex(A => A.Price).IsUnique();//设置唯一索引
除了上述的方法外,我们还可以通过数据注解的方式完成字段最大长度设定、是否能为Null等设置,例如:
[Table("T_Cats")] public class Cat { //当遇到 Int、long、ShortInt、Guid类型时,根据约定,该字段会被设置为主键 public Guid catId { get; set; } [Required] [MaxLength(50)] public string catName { get; set; } [MaxLength(50)] public string sex { get; set; } }
这种方式虽说简单,但,还是建议大家使用IEntityTypeConfiguration<>的方式进行绑定
例如有这么个变态需求,当使用MySQL数据时,希望在数据库中表名为M_Cats,当在SQLServer数据库中是希望表名为T_Cats,那么如果你使用数据注解的方式就无法完成这个工作,使用IEntityTypeConfiguration<>的方式则可以在void Configure(EntityTypeBuilder<Person> builder)方法中写相关的逻辑代码。
1.3、全局过滤器【用于软删除等场景】
builder.HasQueryFilter(A => A.Isdeleted == false);
EFCore查询是会自动带上这个查询条件
1.4、创建数据库上下文

public class wechatDbContext : DbContext { public DbSet<Book> Books { get; set; } public DbSet<Person> Persons { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlServer("Data Source=LAPTOP-84R6S0FB;Initial Catalog=wechat;Integrated Security=True"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); //从当前程序集命名空间加载所有的IEntityTypeConfiguration modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); } }
OnConfiguring 方法初始化数据库链接字符串
OnModelCreating 从当前程序集命名空间加载所有的IEntityTypeConfiguration绑定的实体
1.5、通过程序包控制台,生成数据库
Add-Migration initDb -- Update-Database
Add-Migration 的语法为 Add-Migration + 备注,每次备注不允许一样
Update_DataBase 生成或更新数据库
当然,如果在实际编码中修改字段的长度、字段类型,新增字段、删除字段等操作后,需要执行 Add-Migration 和 Update-DataBase ,执行后数据库才会做出响应的改变。
1.5.1、观察生生的数据库
1.5.2、数据库的回滚及恢复
在CodeFirst中会在数据库中生成一张名为[__EFMigrationsHistory]的表,这张表记录了操作者通过Migration指令操作数据库的记录
注:这张表的数据非常重要,请勿删除、修改
我们可以通过这张表生成的数据还回滚数据库
Update-Database 20220921080741_AddFrild5
-- 【20220921080741_AddFrild5 对应上述第6行记录值】
执行该指令后,数据库会回滚到 20220921080741_AddFrild5的状态
但是EFcore 项目中自动生成的类文件 20220921093339_AddFrild7 并不会被删除。
因此,如果你想恢复到 20220921093339_AddFrild7 状态,只需再重新执行
Update-Database
如果确实不想再恢复数据库到 20220921093339_AddFrild7 状态,您可以通过执行以下指令删除EFcore自动生成的 20220921093339_AddFrild7.cs类 【注意,请勿手动删除,防止误删,错删】
Remove-migration
1.5.3、通过指令,获取库创建表的脚本
script-migration
执行完毕后 ,会生成一个SQL文件
script-migration 适用于在生产环境上线时使用
script-migration D 生成D 到最后一次变更的脚本
script-migration D F 生成D 到 F区间变更的脚本,例如
script-migration 20220921075812_AddFrild3 20220921080741_AddFrild5
这种方式适用于线上数据库已存在,开发环境于线上不同时使用,用于更新线上数据库。
1.6、简单测试
由于DbContext 继承了 IDisposable 、因此在编程时,我们需要使用Using ,这样做是为了能够及时的释放占用的资源。
1.6.1、简单查看EFCORE生成的SQL语句:

static async Task update(int addnum) { using (wechatDbContext context = new wechatDbContext()) { var linq = context.Books.Where(A => A.Title == "平凡的世界"); var sql =linq.ToQueryString(); Console.WriteLine("linq转化为的SQL语句为:" + sql); var eef = linq.FirstOrDefault(); eef.Price = 122; await context.SaveChangesAsync(); } }
此方式近支持IQueryable<T>类型的查询语句
简单测试如下:

using System; using System.Linq; using System.Threading.Tasks; namespace EfCore { internal class Program { static async Task Main(string[] args) { var tsk_1 = add(); var tsk_2 = update(await tsk_1); var tsk_3 = delete(await tsk_1); await tsk_2; await tsk_3; Console.WriteLine("执行完毕"); Console.Read(); } static async Task<int> add() { using (wechatDbContext context = new wechatDbContext()) { Book bok = new Book(); bok.Title = "钢铁是怎么练成的"; bok.Price = 180; bok.Pubdate = DateTime.Now; context.Books.Add(bok); Book bok2 = new Book(); bok2.Title = "平凡的世界"; bok2.Price = 110; bok2.Pubdate = DateTime.Now; context.Books.Add(bok2); return await context.SaveChangesAsync(); } } static async Task update(int addnum) { using (wechatDbContext context = new wechatDbContext()) { var ef = context.Books.Where(A => A.Title == "平凡的世界").FirstOrDefault(); ef.Price = 122; await context.SaveChangesAsync(); } } static async Task delete(int addNum) { using (wechatDbContext context = new wechatDbContext()) { var ef = context.Books.Where(A => A.Title == "钢铁是怎么练成的").FirstOrDefault(); context.Books.Remove(ef); await context.SaveChangesAsync(); } } } }
@天才卧龙的bolero
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端