C#中的EFCore数据库连接
核心组件:微软推出的一个orm框架, 主要作用简化对数据库的操作:1:Sql生成器,2:实体关系映射,sql解析器( 范型+反射 ),3: 代码生成器。之后针对实例化数据库实体模型对象建立的ef框架
EFcore实体框架,基于ORM框架(Object Relational Maqqing)对象--关系--映射,意思是一个对象(实体类)表示一个表。一个上下文类表示一个数据库。
ORM框架:不用考虑SQL语句怎么写,以类为单位去操作数据库,以面向对象的思想对数据库进行操作。可以搭配【linq查询条件】一起使用。
两种模式:DB First 数据库优先;code First 代码优先。
EFcore其实是一个提供程序包(dll)文件,连接数据库用的。需要NuGet包管理器下载到项目中才能用。搜索entity就可以找到。
下载名称如下:
EF框架核心包(必装):Microsoft.EntityFrameworkCore
SQL server中型数据库:Microsoft.EntityFrameworkCore.SqlServer
MySQL小型数据库:MySQL.Data.EntityFrameworkCore
Oracle大型数据库:Oracle.EntityFrameworkCore
SQLite移动端数据库: Microsoft.EntityFrameworkCore.SQLite
InMemory做性能测试的:Microsoft.EntityFrameworkCore.InMemory
API 数据接口:Microsoft.EntityFrameworkCore.Cosmos
EF框架命令工具包:Microsoft.EntityFrameworkCore.Tools 必须安装后面你才能使用一些命令,比如数据库迁移;命令控制台:工具--->NuGet程序包管理器--->程序包管理控制台。即可输入命令。
拼接数据库连接字符串:
服务器名称3种方法:1.Data Source = (localdb)\ProjectModels; 2.server=.;3.addr=.;
数据库名称两种:1、database=数据库名;2、initial catalog=数据库名;
安全信息凭证两种:1、trusted_connection=true;2、Integrated Security = false; 【true表示不需要账号密码登录数据库,false表示必须要账号密码登录,默认值所以需要配合:uid=sa;pwd=123456;一起用】。
属性:MultipleActiveResultSets=true;表示提高访问数据库的效率
string connString = @"server=.;database=OA;uid=sa;pwd=123456;TrustServerCertificate=True;";//数据库连接字符串
string connString = @"server=.;database=OA;trusted_connection=true;MultipleActiveResultSets=true;";//数据库连接字符串
1.code First:用代码来生成数据库(比较灵活)
首先创建个实体类,实体类最好建立Models文件夹专门存放。通过实体类转换成数据库。
//Models实体类:做数据传输用,一个类表示一个表。 public class userinfo { public int id { get; set; }//编号,默认约束指定:第一个属性名包含id大小都可以,默认为主键,如:uID public string name { get; set; }//姓名 public string sex { get; set; }//性别 public int age { get; set; }//年龄 }
数据库上下文类:操作数据库用的
数据库上下文类(管道:操作ef框架的一个类)可以把上下文类看成一个数据库,表看成一个属性
必须继承DbContext是ef框架提提供给我们的,封装了对数据库的增删改查等功能,功能不够要添加一下自己的东西比如:新表等等
using ConsoleApp1.Models;//实体类文件夹自己创建的 using Microsoft.EntityFrameworkCore;//使用EFCore框架必须要引入。 Console.WriteLine("这个代码没用,只是顶级程序入口没有代码,随便输入一句话指定入口Main方法而已"); //创建一个操作OA数据库的上下文类 public class NetContext : DbContext//必须继承ef框架提供的DbContext类,我们创建的上下文类是用来添加新功能的 { //构造方法主要ef框架在用,有时用户也可能用到 public NetContext() { } //其实这两个构造也是父类的构造,其实参数是给父类传递的。参数的意思是自动获取到连接数据库字符串 public NetContext(DbContextOptions<NetContext> options):base(options) { } public DbSet<userinfo> userinfo { get; set; }//把表看成一个属性:用一个字段属性表示一个表。连接model用与生成表,默认属性名为表名 //重写实现父类虚方法;配置方法:作用是配置数据库(参数:选项创建者) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { string connString = @"server=.;database=OA;trusted_connection=true;MultipleActiveResultSets=true;";//数据库连接字符串 optionsBuilder.UseSqlServer(connString); } }
创建好后就可以执行数据库迁移两个命令:工具--NuGet包管理器--控制台
add-migration init-Mig (生成迁移代码 init-Mig是迁移的名称) 得到一个迁移文件夹
update-database(执行到数据库)
注意:provider: SSL Provider, error: 0 - 证书链是由不受信任的颁发机构颁发的
如果勾选了,连接字符串需要设置:Encrypt=True;TrustServerCertificate=True;
另外还可以用代码来删除和创建数据库,这种会整个数据库重新创建一遍(数据会丢失)
using (var db = new userContext()) { db.Database.EnsureDeleted();//删除数据库 db.Database.EnsureCreated();//创建数据库 }
2、DB First 用数据库来生成代码(了解)
首先要安装一个反向过程设计包:Microsoft.EntityFrameworkCore.Design 就是把数据库转换成实体类,不需要手动写上下文类,用命令来自动生成。最终还是用上下文类操作数据库。
创建一个数据库做演示
create database OA go use OA go create table userinfo ( id int primary key identity(1,1), name nvarchar(20), sex char(5), age int, ) insert into userinfo values('张三','男',19)select * from userinfo --修改: update userinfo set name='大王',sex='男',age=16 where id=2 --删除: delete from userinfo where id=2
生成上下文类命令3个部分:1.创建命令 2.连接数据库字符串 3.数据库程序包
Scaffold-DbContext "server=.;database=OA;trusted_connection=true;MultipleActiveResultSets=true;" Microsoft.EntityFrameworkCore.SqlServer
规范命令:继续往后加
-Context NameContext 上下文名称规范
-outputdir Models 实体类生成在Models文件夹下
-contextdir Data 上下文类在Data目录下
-Tables userinfo 我在想生成userinfo其他表不需要,需要就,往后面加,默认生成所有表。
-DataAnnotations 生成数据注解。也就是【特性】
-Force 强制覆盖
常用的特性约束
配置约束的三种方式:
1.默认约束。如:实体类表的第一个字段只要包含id大小写都可以:StudentID,默认就是主键。
2.数据注解(通过【特性】进行约束)执行优先级:FluentAPI高于----特性高于-----默认约束
3.FluentAPI(通过接口来配置,最强大)FluentAPI会覆盖前面两种约束
using System.ComponentModel.DataAnnotations;//系统特性 using System.ComponentModel.DataAnnotations.Schema;//数据库特性 using Microsoft.EntityFrameworkCore;//efcore框架 namespace ConsoleApp1.Models { [NotMapped, Table("userinfo")]//不想同步当前表到数据库。更改表名。多特性可以写一起用逗号隔开。 public partial class Userinfo { [Key]//主键,不过一般都用默认值,这个特性用于不包含id的属性名。 public int Id { get; set; } [Column("name")]//Column是列的意思,这里表示更改属性名(列名),Column列名多参数可以用逗号隔开构造方法重载的。 [StringLength(20)]//字符串长度,参数最大长度。 [Display(Name = "姓名")]//UI界面会显示别名 public string Name { get; set; } [StringLength(5,MinimumLength = 1,ErrorMessage ="错误信息:长度超出范围")] [Unicode(false)]//非unicode,表示可以包含汉字+特殊字符。如果数据库支持Unicode 类型时,UnicodeAttribute 会被忽略。 [Required]//不为空 [Column(Order = 3)]//改为第3列,默认id为0开始排序 public string Sex { get; set; } //默认不为空 [NotMapped]//不想映射这个字段到数据库, [Column(TypeName = "money")] //在数据库显示的是money类型表示金钱类型,对应decimal类型,这里随便写的int [ForeignKey("Stu")]//外键约束,把当前属性,当成表Student的外键,如果当前属性名为StudentID之类的默认就是外键,就不需要特性了。 public int? Age { get; set; } //问号表示可以为空null public virtual Student Stu { get; set; }//导航属性:导航属性要添加为virtual虚属性,c#特性新功能,重写数据延迟加载。 } public partial class Student//建立关系,1对多,多的那一边要有导航属性和外键关联。如{1,2{1,2,3,4...}},只要是关联都定义为虚属性。 { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<Userinfo> Userinfo { get; set; } //建立关系用集合ICollection,集合里必须有导航属性和外键属性。 public virtual Userinfo Userinfo1 { get; set; } //这样就是1对1的关系不是集合,而是两把都是对应的导航属性。 } }
给表添加默认数据OnModelCreating
创建两个实体类做表
using System.ComponentModel.DataAnnotations;//系统特性 namespace ConsoleApp1.Models { //Models实体类:做数据传输用,一个类表示一个表。 public class userinfo { [Key] public int usersid { get; set; }//编号,默认约束指定:第一个属性名包含id大小都可以,默认为主键,如:uID [StringLength(10)] [Display(Name = "姓名")] public string? name { get; set; }//姓名 public DateTime time { get; set; }//时间 //public virtual Student Student { get; set; }//假如我在这边也使用导航属性,那么就变成1对1关系了,使用一个usersid,另外Studentid就不需要。 public virtual ICollection<Student> Student { get; set; }//集合导航属性1对多接收多的那一边,如;Student有很多usersid为2 } public class Student { public int Studentid { get; set; } public int usersid { get; set; }//1对多,多的一边要加对应关联id。 public GetSex sex { get; set; }//性别 public int age { get; set; }//年龄 //导航属性都要定义为虚属性virtual,框架会重写,添加新功能。 public virtual userinfo users{ get; set; } //表Student为多个数据,对应一个userinfo中的一个数据必须指定对应的一个字段如usersid //public virtual ICollection<userinfo> userss{ get; set; }//两边都有叫多对多,不需要引导id会自动生成一个中间表。 } public enum GetSex { 男,女 } //枚举默认,男=0 }
创建数据库上下文类
using ConsoleApp1.Models; using Microsoft.EntityFrameworkCore;//使用EFCore框架必须要引入。 namespace ConsoleApp1.Data { //创建一个操作OA数据库的上下文类 public class OAContext : DbContext { public DbSet<userinfo> userinfo { get; set; } public DbSet<Student> Student { get; set; } //重写实现父类虚方法;作用是配置数据库 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { string connString = @"server=.;database=OA;trusted_connection=true;MultipleActiveResultSets=true;";//数据库连接字符串 optionsBuilder.UseSqlServer(connString); } //数据播种,默认添加默认数据 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<userinfo>().HasData(new List<userinfo> { new userinfo{ usersid = 1,name="张三",time=DateTime.UtcNow }, new userinfo{ usersid = 2,name="李四",time=DateTime.UtcNow }, new userinfo{ usersid = 3,name="王五",time=DateTime.UtcNow } }); modelBuilder.Entity<Student>().HasData(new List<Student> { new Student{ Studentid=1, usersid = 1,sex=GetSex.男,age = 18 }, new Student{ Studentid=2, usersid = 1,sex=GetSex.女,age = 19 }, new Student{ Studentid=3, usersid = 3,sex=GetSex.男,age = 20 } }); } } }
数据加载(查询)
查询单表的所有数据(通常搭配linq查询)
using ConsoleApp1.Data;//上下文的文件夹 using Microsoft.EntityFrameworkCore;//ef框架,这里AsNoTracking方法取消跟踪使用到 //using括号内对象执行玩自动释放空间,因为ef数据库操作类DbContext实现了IDisposable接口,只要实现这个接口的对象都需要手动close关闭。 using (var db = new OAContext()) //上下文类继承的是DbContext,所以需要手动关闭,而using关键字,内部实现了自动close关闭功能。 { //查询不需要追踪,增删改才需要,默认ef是打开追踪模式。可以用AsNoTracking方法关闭追踪,提高查询效率。 var users = db.userinfo.AsNoTracking().ToList(); foreach (var user in users) Console.WriteLine($"{user.usersid},{user.name},{user.time}"); }
查询关联表,一级用Include二级以后用ThenInclude。
using ConsoleApp1.Data;//上下文的文件夹 using Microsoft.EntityFrameworkCore;//ef框架 using (var db = new OAContext()) { var stu = db.Student.AsNoTracking().Include(s=>s.users).ToList();//二级三级以上往后加ThenInclude(c=>c.关联名)即可 foreach (var s in stu) Console.WriteLine($"{s.age},{s.sex},{s.users.name},{s.users.time}"); var ss = stu.FirstOrDefault();//显示查询到的第一条数据 Console.WriteLine($"{ss.age},{ss.sex},{ss.users.name},{ss.users.time}");//如果users也是一个集合也可以用ss.users.FirstOrDefault().name这样查。 }
匹配加载:大家叫显示加载,用于查询关联表,提高效率,匹配查找减少不需要的数据。
using ConsoleApp1.Data;//上下文的文件夹 using Microsoft.EntityFrameworkCore;//ef框架 using (var db = new OAContext()) { var user = db.userinfo.First(); //拿第一条数据,或者筛选出一条数据,通过Entry方法匹配查找 db.Entry(user).Collection(c=>c.Student).Load();//针对集合用Collection,加载用load Console.WriteLine($"{user.name}:{user.time}:{user.Student.FirstOrDefault().sex}"); var stu = db.Student.First();//查第一个的区别,First为空会报错,FirstOrDefault返回默认值0 db.Entry(stu).Reference(c => c.users).Load();//针对单个实体用Reference Console.WriteLine($"{stu.sex}:{stu.age}:{stu.users.name}:{stu.users.time}"); }
条件查询,选择性加载用到linq里的select方法
using ConsoleApp1.Data;//上下文的文件夹 using Microsoft.EntityFrameworkCore;//ef框架 using (var db = new OAContext()) { var user = db.userinfo.AsTracking() //开启追踪模式,还可以userinfo.Where(a=>a.name=="条件") .Select(x => new modle //转换最好定义一个实体类,匿名对象到时候还需要在转换一次。 { name = x.name,//查到的数据,在存入实体类 time = x.time //不定义modle指定匿名对象是泛型数组 }).ToList(); foreach (var item in user) Console.WriteLine($"{item.name}:{item.time}"); } class modle { public string name { get; set; } public DateTime time { get; set; } }
延迟加载:1.必须安装:Microsoft.EntityFrameworkCore.Proxies 包,2.配置上下文类中的OnConfiguring方法,3.导航属性必须是虚属性virtual
//重写实现父类虚方法;作用是配置数据库,这里配置第二步的延迟加载。 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseLazyLoadingProxies();//配置延迟加载 string connString = @"server=.;database=OA;trusted_connection=true;MultipleActiveResultSets=true;";//数据库连接字符串 optionsBuilder.UseSqlServer(connString); }
演示查询延迟加载
using ConsoleApp1.Data;//上下文的文件夹 using Microsoft.EntityFrameworkCore;//ef框架 using (var db = new OAContext()) { var user = db.userinfo.FirstOrDefault(); var stu = user.Student.FirstOrDefault(); Console.WriteLine($"{user.name}:{stu.sex}"); }
增删改
添加
using ConsoleApp1.Data;//上下文的文件夹 using ConsoleApp1.Models;//实体类 using Microsoft.EntityFrameworkCore;//ef框架 using (var db = new OAContext()) { userinfo u = new userinfo(); u.name = "赵六"; u.time = DateTime.Now; var id = db.Add(u);//在内存进行添加,返回对象id值,可以直接实体类中取值。 var count = db.SaveChanges();//保存到数据库,返回受影响行数。增删改都需要保存数据库。 Console.WriteLine($"添加{count}条数据id值:{u.usersid}"); }
修改
using (var db = new OAContext()) { var u = db.userinfo.Find(5);//通过id查询到数据 u.name = "王大妈"; u.time = DateTime.Now; db.userinfo.Update(u);//内存里执行修改 u = db.userinfo.Find(6);//通过id查询到数据 u.name = "王大妈2"; u.time = DateTime.Now; db.userinfo.Update(u);//内存里执行修改 var count = db.SaveChanges();//保存到数据库,其实他是一个事物处理,可以保存所有操作,写一个就可以,一个出错,全部撤销。 Console.WriteLine($"修改{count}条数据"); }
删除(默认是级联删除)
using (var db = new OAContext()) { var u = db.userinfo.SingleOrDefault(a => a.usersid == 1);//通过id查询先到数据,SingleOrDefault如果大于1异常,为空返回null db.userinfo.Remove(u);//在内存里删除 var count = db.SaveChanges();//保存到数据库。 Console.WriteLine($"删除{count}条数据"); }
软删除:通过删除,实现数据隐藏功能,并非真的删除,比如管理员权限操作等等,也可以用此方法。
首先在上下文中配置过滤器
//数据播种,默认添加默认数据,操作实体类的配置方法。 protected override void OnModelCreating(ModelBuilder modelBuilder) { //配置软删除过滤器:表示可以查询到年龄为20的数据,其他的就查不到。建议改为:(false和true),懒得写了。 modelBuilder.Entity<Student>().HasQueryFilter(s => s.age == 20);//可以重新定义一个实现布尔类型的判断。 //初始化数据 modelBuilder.Entity<userinfo>().HasData(new List<userinfo> { new userinfo{ usersid = 1,name="张三",time=DateTime.UtcNow }, new userinfo{ usersid = 2,name="李四",time=DateTime.UtcNow }, new userinfo{ usersid = 3,name="王五",time=DateTime.UtcNow } }); modelBuilder.Entity<Student>().HasData(new List<Student> { new Student{ Studentid=1, usersid = 1,sex=GetSex.男,age = 18 }, new Student{ Studentid=2, usersid = 2,sex=GetSex.女,age = 19 }, new Student{ Studentid=3, usersid = 3,sex=GetSex.男,age = 20 } }); }
执行权限修改操作,完成软删除功能
using ConsoleApp1.Data;//上下文的文件夹 using ConsoleApp1.Models;//实体类 using Microsoft.EntityFrameworkCore;//ef框架 using (var db = new OAContext()) { var s = db.Student.SingleOrDefault(a => a.Studentid == 2);//通过id查询先到数据,SingleOrDefault如果大于1异常,为空返回null s.age = 20;//可以改成布尔值true和false 懒得写代码才用年龄判断。 db.Student.Update(s);//这里是修改数据权限,不是真的删除数据。 var count = db.SaveChanges();//保存到数据库。 Console.WriteLine($"删除{count}条数据"); }
FromSql和ExecuteSql的使用
用来执行常规的SQL语句
using (userContext db = new userContext()) { db.Database.ExecuteSql($"insert into uinfo values('张三22')"); db.Database.ExecuteSql($"delete from uinfo where id=2"); db.Database.ExecuteSql($"update uinfo set name='王五' where id=1");//用sql语句执行增删改 var users = db.uinfo.FromSql($"select *from uinfo").ToList();//用SQL语句查询 foreach(var user in users) Console.WriteLine($"{user.id}, {user.name}"); }
为了避免SQL注入建议使用SqlParameter()对象来传递参数,使用这个对象时框架的方法也修改改为:ExecuteSqlRaw(增删改)和 FromSqlRaw(查)
using (userContext db = new userContext()) { IDbContextTransaction trans = null;//1.创建一个事务:作用是当第一个执行完成,第二个出现错误,都会回滚到起点重新来过。 try { db.Database.ExecuteSqlRaw($"insert into uinfo values(@name)",new SqlParameter("@name", "张三33")); db.Database.ExecuteSqlRaw($"update uinfo set name=@name where id=@id", new[] { new SqlParameter("@name", "张三22"), new SqlParameter("@id", 3) }); var users = db.uinfo.FromSqlRaw($"select *from uinfo where name like @name", new SqlParameter("@name", "张%")).ToList();//原生SQL语句的模糊查询 foreach (var user in users) Console.WriteLine($"{user.id}, {user.name}"); trans?.Commit();//2.提交事务 } catch(Exception ex) { if(trans !=null)trans?.Rollback();//3.事务回滚,如果用Using包裹的话,可以不需要手动Rollback,走完Using会自动回滚。 } finally { trans?.Dispose();//4.销毁事务 } }
另外还有有个是多线程异步处理ExecuteSqlRawAsync